form-builder-pro 0.0.1
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 +118 -0
- package/dist/index.css +1204 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +107 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +1154 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1125 -0
- package/dist/index.mjs.map +1 -0
- package/dist/web-components.d.mts +3 -0
- package/dist/web-components.d.ts +3 -0
- package/dist/web-components.js +1139 -0
- package/dist/web-components.js.map +1 -0
- package/dist/web-components.mjs +1114 -0
- package/dist/web-components.mjs.map +1 -0
- package/package.json +58 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1125 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { defaultDropAnimationSideEffects, useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCorners, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core';
|
|
4
|
+
import { sortableKeyboardCoordinates, useSortable, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
5
|
+
import { create } from 'zustand';
|
|
6
|
+
import * as Icons from 'lucide-react';
|
|
7
|
+
import { Undo, Redo, Trash2, Eye, Save, GripVertical, Plus, X } from 'lucide-react';
|
|
8
|
+
import { clsx } from 'clsx';
|
|
9
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
10
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
11
|
+
import { createPortal } from 'react-dom';
|
|
12
|
+
import ReactDOM from 'react-dom/client';
|
|
13
|
+
|
|
14
|
+
var __defProp = Object.defineProperty;
|
|
15
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
16
|
+
var __publicField = (obj, key, value) => {
|
|
17
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
18
|
+
return value;
|
|
19
|
+
};
|
|
20
|
+
var FormSchemaValidation = z.object({
|
|
21
|
+
id: z.string(),
|
|
22
|
+
title: z.string(),
|
|
23
|
+
sections: z.array(z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
title: z.string(),
|
|
26
|
+
fields: z.array(z.any())
|
|
27
|
+
// Deep validation can be added if needed
|
|
28
|
+
}))
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// src/core/constants.ts
|
|
32
|
+
var generateId = () => Math.random().toString(36).substring(2, 9);
|
|
33
|
+
var FIELD_TYPES = [
|
|
34
|
+
{ type: "text", label: "Text Input", icon: "Type" },
|
|
35
|
+
{ type: "textarea", label: "Text Area", icon: "AlignLeft" },
|
|
36
|
+
{ type: "number", label: "Number", icon: "Hash" },
|
|
37
|
+
{ type: "email", label: "Email", icon: "Mail" },
|
|
38
|
+
{ type: "phone", label: "Phone", icon: "Phone" },
|
|
39
|
+
{ type: "date", label: "Date Picker", icon: "Calendar" },
|
|
40
|
+
{ type: "select", label: "Dropdown", icon: "ChevronDown" },
|
|
41
|
+
{ type: "checkbox", label: "Checkbox", icon: "CheckSquare" },
|
|
42
|
+
{ type: "radio", label: "Radio Group", icon: "CircleDot" },
|
|
43
|
+
{ type: "toggle", label: "Toggle", icon: "ToggleLeft" },
|
|
44
|
+
// Lucide icon names, will be mapped later
|
|
45
|
+
{ type: "file", label: "File Upload", icon: "Upload" }
|
|
46
|
+
];
|
|
47
|
+
var DEFAULT_FIELD_CONFIG = {
|
|
48
|
+
text: { label: "Text Input", placeholder: "Enter text...", width: "100%" },
|
|
49
|
+
textarea: { label: "Text Area", placeholder: "Enter description...", width: "100%" },
|
|
50
|
+
number: { label: "Number", placeholder: "0", width: "50%" },
|
|
51
|
+
email: { label: "Email", placeholder: "example@email.com", width: "100%", validation: [{ type: "email", message: "Invalid email" }] },
|
|
52
|
+
phone: { label: "Phone", placeholder: "+1 234 567 8900", width: "100%" },
|
|
53
|
+
date: { label: "Date", width: "50%" },
|
|
54
|
+
select: { label: "Dropdown", options: [{ label: "Option 1", value: "opt1" }, { label: "Option 2", value: "opt2" }], width: "100%" },
|
|
55
|
+
checkbox: { label: "Checkbox", width: "100%" },
|
|
56
|
+
radio: { label: "Radio Group", options: [{ label: "Option 1", value: "opt1" }, { label: "Option 2", value: "opt2" }], width: "100%" },
|
|
57
|
+
toggle: { label: "Toggle", width: "50%" },
|
|
58
|
+
file: { label: "File Upload", width: "100%" }
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/core/useFormStore.ts
|
|
62
|
+
var INITIAL_SCHEMA = {
|
|
63
|
+
id: "form_1",
|
|
64
|
+
title: "My New Form",
|
|
65
|
+
sections: [
|
|
66
|
+
{
|
|
67
|
+
id: generateId(),
|
|
68
|
+
title: "Section 1",
|
|
69
|
+
fields: []
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
var useFormStore = create((set, get) => ({
|
|
74
|
+
schema: INITIAL_SCHEMA,
|
|
75
|
+
selectedFieldId: null,
|
|
76
|
+
history: [INITIAL_SCHEMA],
|
|
77
|
+
historyIndex: 0,
|
|
78
|
+
isPreviewMode: false,
|
|
79
|
+
setSchema: (schema) => set({ schema }),
|
|
80
|
+
togglePreview: () => set((state) => ({ isPreviewMode: !state.isPreviewMode })),
|
|
81
|
+
addSection: () => {
|
|
82
|
+
const { schema, history, historyIndex } = get();
|
|
83
|
+
const newSection = {
|
|
84
|
+
id: generateId(),
|
|
85
|
+
title: `Section ${schema.sections.length + 1}`,
|
|
86
|
+
fields: []
|
|
87
|
+
};
|
|
88
|
+
const newSchema = { ...schema, sections: [...schema.sections, newSection] };
|
|
89
|
+
set({
|
|
90
|
+
schema: newSchema,
|
|
91
|
+
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
92
|
+
historyIndex: historyIndex + 1
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
removeSection: (sectionId) => {
|
|
96
|
+
const { schema, history, historyIndex } = get();
|
|
97
|
+
const newSchema = {
|
|
98
|
+
...schema,
|
|
99
|
+
sections: schema.sections.filter((s) => s.id !== sectionId)
|
|
100
|
+
};
|
|
101
|
+
set({
|
|
102
|
+
schema: newSchema,
|
|
103
|
+
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
104
|
+
historyIndex: historyIndex + 1
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
updateSection: (sectionId, updates) => {
|
|
108
|
+
const { schema, history, historyIndex } = get();
|
|
109
|
+
const newSchema = {
|
|
110
|
+
...schema,
|
|
111
|
+
sections: schema.sections.map(
|
|
112
|
+
(s) => s.id === sectionId ? { ...s, ...updates } : s
|
|
113
|
+
)
|
|
114
|
+
};
|
|
115
|
+
set({
|
|
116
|
+
schema: newSchema,
|
|
117
|
+
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
118
|
+
historyIndex: historyIndex + 1
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
moveSection: (activeId, overId) => {
|
|
122
|
+
const { schema, history, historyIndex } = get();
|
|
123
|
+
const oldIndex = schema.sections.findIndex((s) => s.id === activeId);
|
|
124
|
+
const newIndex = schema.sections.findIndex((s) => s.id === overId);
|
|
125
|
+
if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex)
|
|
126
|
+
return;
|
|
127
|
+
const newSections = [...schema.sections];
|
|
128
|
+
const [movedSection] = newSections.splice(oldIndex, 1);
|
|
129
|
+
newSections.splice(newIndex, 0, movedSection);
|
|
130
|
+
const newSchema = { ...schema, sections: newSections };
|
|
131
|
+
set({
|
|
132
|
+
schema: newSchema,
|
|
133
|
+
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
134
|
+
historyIndex: historyIndex + 1
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
addField: (sectionId, type, index) => {
|
|
138
|
+
const { schema, history, historyIndex } = get();
|
|
139
|
+
const newField = {
|
|
140
|
+
id: generateId(),
|
|
141
|
+
type,
|
|
142
|
+
...DEFAULT_FIELD_CONFIG[type]
|
|
143
|
+
};
|
|
144
|
+
const newSchema = {
|
|
145
|
+
...schema,
|
|
146
|
+
sections: schema.sections.map((s) => {
|
|
147
|
+
if (s.id === sectionId) {
|
|
148
|
+
const newFields = [...s.fields];
|
|
149
|
+
if (typeof index === "number") {
|
|
150
|
+
newFields.splice(index, 0, newField);
|
|
151
|
+
} else {
|
|
152
|
+
newFields.push(newField);
|
|
153
|
+
}
|
|
154
|
+
return { ...s, fields: newFields };
|
|
155
|
+
}
|
|
156
|
+
return s;
|
|
157
|
+
})
|
|
158
|
+
};
|
|
159
|
+
set({
|
|
160
|
+
schema: newSchema,
|
|
161
|
+
selectedFieldId: newField.id,
|
|
162
|
+
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
163
|
+
historyIndex: historyIndex + 1
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
removeField: (fieldId) => {
|
|
167
|
+
const { schema, history, historyIndex } = get();
|
|
168
|
+
const newSchema = {
|
|
169
|
+
...schema,
|
|
170
|
+
sections: schema.sections.map((s) => ({
|
|
171
|
+
...s,
|
|
172
|
+
fields: s.fields.filter((f) => f.id !== fieldId)
|
|
173
|
+
}))
|
|
174
|
+
};
|
|
175
|
+
set({
|
|
176
|
+
schema: newSchema,
|
|
177
|
+
selectedFieldId: null,
|
|
178
|
+
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
179
|
+
historyIndex: historyIndex + 1
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
updateField: (fieldId, updates) => {
|
|
183
|
+
const { schema, history, historyIndex } = get();
|
|
184
|
+
const newSchema = {
|
|
185
|
+
...schema,
|
|
186
|
+
sections: schema.sections.map((s) => ({
|
|
187
|
+
...s,
|
|
188
|
+
fields: s.fields.map((f) => f.id === fieldId ? { ...f, ...updates } : f)
|
|
189
|
+
}))
|
|
190
|
+
};
|
|
191
|
+
set({
|
|
192
|
+
schema: newSchema,
|
|
193
|
+
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
194
|
+
historyIndex: historyIndex + 1
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
selectField: (fieldId) => set({ selectedFieldId: fieldId }),
|
|
198
|
+
moveField: (activeId, overId, activeSectionId, overSectionId) => {
|
|
199
|
+
const { schema, history, historyIndex } = get();
|
|
200
|
+
const newSections = schema.sections.map((s) => ({
|
|
201
|
+
...s,
|
|
202
|
+
fields: [...s.fields]
|
|
203
|
+
}));
|
|
204
|
+
const activeSectionIndex = newSections.findIndex((s) => s.id === activeSectionId);
|
|
205
|
+
const overSectionIndex = newSections.findIndex((s) => s.id === overSectionId);
|
|
206
|
+
if (activeSectionIndex === -1 || overSectionIndex === -1)
|
|
207
|
+
return;
|
|
208
|
+
const activeSection = newSections[activeSectionIndex];
|
|
209
|
+
const overSection = newSections[overSectionIndex];
|
|
210
|
+
const activeFieldIndex = activeSection.fields.findIndex((f) => f.id === activeId);
|
|
211
|
+
const overFieldIndex = overSection.fields.findIndex((f) => f.id === overId);
|
|
212
|
+
if (activeFieldIndex === -1)
|
|
213
|
+
return;
|
|
214
|
+
if (activeSectionId === overSectionId) {
|
|
215
|
+
if (activeFieldIndex === overFieldIndex)
|
|
216
|
+
return;
|
|
217
|
+
const [movedField] = activeSection.fields.splice(activeFieldIndex, 1);
|
|
218
|
+
activeSection.fields.splice(overFieldIndex, 0, movedField);
|
|
219
|
+
} else {
|
|
220
|
+
const [movedField] = activeSection.fields.splice(activeFieldIndex, 1);
|
|
221
|
+
if (overId === overSectionId) {
|
|
222
|
+
overSection.fields.push(movedField);
|
|
223
|
+
} else {
|
|
224
|
+
const insertIndex = overFieldIndex >= 0 ? overFieldIndex : overSection.fields.length;
|
|
225
|
+
overSection.fields.splice(insertIndex, 0, movedField);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const newSchema = { ...schema, sections: newSections };
|
|
229
|
+
set({
|
|
230
|
+
schema: newSchema,
|
|
231
|
+
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
232
|
+
historyIndex: historyIndex + 1
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
undo: () => {
|
|
236
|
+
const { history, historyIndex } = get();
|
|
237
|
+
if (historyIndex > 0) {
|
|
238
|
+
set({
|
|
239
|
+
schema: history[historyIndex - 1],
|
|
240
|
+
historyIndex: historyIndex - 1
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
redo: () => {
|
|
245
|
+
const { history, historyIndex } = get();
|
|
246
|
+
if (historyIndex < history.length - 1) {
|
|
247
|
+
set({
|
|
248
|
+
schema: history[historyIndex + 1],
|
|
249
|
+
historyIndex: historyIndex + 1
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
canUndo: () => get().historyIndex > 0,
|
|
254
|
+
canRedo: () => get().historyIndex < get().history.length - 1
|
|
255
|
+
}));
|
|
256
|
+
var Toolbar = () => {
|
|
257
|
+
const { undo, redo, canUndo, canRedo, setSchema, togglePreview, isPreviewMode } = useFormStore();
|
|
258
|
+
const handleSave = () => {
|
|
259
|
+
const schema = useFormStore.getState().schema;
|
|
260
|
+
console.log("Saved Schema:", JSON.stringify(schema, null, 2));
|
|
261
|
+
alert("Schema saved to console!");
|
|
262
|
+
};
|
|
263
|
+
const handleClear = () => {
|
|
264
|
+
if (confirm("Are you sure you want to clear the form?")) {
|
|
265
|
+
setSchema({
|
|
266
|
+
id: "form_" + Date.now(),
|
|
267
|
+
title: "New Form",
|
|
268
|
+
sections: []
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-4 border-b bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-800", children: [
|
|
273
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
274
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-bold bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-transparent mr-4", children: "FormBuilder Pro" }),
|
|
275
|
+
/* @__PURE__ */ jsx(
|
|
276
|
+
"button",
|
|
277
|
+
{
|
|
278
|
+
onClick: undo,
|
|
279
|
+
disabled: !canUndo(),
|
|
280
|
+
className: "p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
|
|
281
|
+
title: "Undo",
|
|
282
|
+
children: /* @__PURE__ */ jsx(Undo, { size: 18 })
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
/* @__PURE__ */ jsx(
|
|
286
|
+
"button",
|
|
287
|
+
{
|
|
288
|
+
onClick: redo,
|
|
289
|
+
disabled: !canRedo(),
|
|
290
|
+
className: "p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
|
|
291
|
+
title: "Redo",
|
|
292
|
+
children: /* @__PURE__ */ jsx(Redo, { size: 18 })
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
] }),
|
|
296
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
297
|
+
/* @__PURE__ */ jsxs(
|
|
298
|
+
"button",
|
|
299
|
+
{
|
|
300
|
+
onClick: handleClear,
|
|
301
|
+
className: "flex items-center px-3 py-2 text-sm font-medium text-red-600 hover:bg-red-50 rounded-md transition-colors",
|
|
302
|
+
children: [
|
|
303
|
+
/* @__PURE__ */ jsx(Trash2, { size: 16, className: "mr-2" }),
|
|
304
|
+
"Clear"
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
),
|
|
308
|
+
/* @__PURE__ */ jsxs(
|
|
309
|
+
"button",
|
|
310
|
+
{
|
|
311
|
+
onClick: togglePreview,
|
|
312
|
+
className: clsx(
|
|
313
|
+
"flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors",
|
|
314
|
+
isPreviewMode ? "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200" : "text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
315
|
+
),
|
|
316
|
+
children: [
|
|
317
|
+
/* @__PURE__ */ jsx(Eye, { size: 16, className: "mr-2" }),
|
|
318
|
+
isPreviewMode ? "Edit" : "Preview"
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
),
|
|
322
|
+
/* @__PURE__ */ jsxs(
|
|
323
|
+
"button",
|
|
324
|
+
{
|
|
325
|
+
onClick: handleSave,
|
|
326
|
+
className: "flex items-center px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md shadow-sm transition-colors",
|
|
327
|
+
children: [
|
|
328
|
+
/* @__PURE__ */ jsx(Save, { size: 16, className: "mr-2" }),
|
|
329
|
+
"Save"
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
] })
|
|
334
|
+
] });
|
|
335
|
+
};
|
|
336
|
+
var getIcon = (name) => {
|
|
337
|
+
const Icon = Icons[name];
|
|
338
|
+
return Icon ? /* @__PURE__ */ jsx(Icon, { size: 16 }) : null;
|
|
339
|
+
};
|
|
340
|
+
var ToolboxItem = ({ type, label, icon }) => {
|
|
341
|
+
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
|
342
|
+
id: `toolbox-${type}`,
|
|
343
|
+
data: {
|
|
344
|
+
type: "toolbox-item",
|
|
345
|
+
fieldType: type
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
const style = transform ? {
|
|
349
|
+
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`
|
|
350
|
+
} : void 0;
|
|
351
|
+
return /* @__PURE__ */ jsxs(
|
|
352
|
+
"div",
|
|
353
|
+
{
|
|
354
|
+
ref: setNodeRef,
|
|
355
|
+
...listeners,
|
|
356
|
+
...attributes,
|
|
357
|
+
style,
|
|
358
|
+
className: "flex items-center p-3 mb-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md cursor-move hover:border-blue-500 hover:shadow-sm transition-all",
|
|
359
|
+
children: [
|
|
360
|
+
/* @__PURE__ */ jsx("span", { className: "mr-3 text-gray-500 dark:text-gray-400", children: getIcon(icon) }),
|
|
361
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-200", children: label })
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
var FieldToolbox = () => {
|
|
367
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-64 bg-gray-50 dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 p-4 overflow-y-auto h-full", children: [
|
|
368
|
+
/* @__PURE__ */ jsx("h2", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-4", children: "Form Fields" }),
|
|
369
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1", children: FIELD_TYPES.map((field) => /* @__PURE__ */ jsx(
|
|
370
|
+
ToolboxItem,
|
|
371
|
+
{
|
|
372
|
+
type: field.type,
|
|
373
|
+
label: field.label,
|
|
374
|
+
icon: field.icon
|
|
375
|
+
},
|
|
376
|
+
field.type
|
|
377
|
+
)) })
|
|
378
|
+
] });
|
|
379
|
+
};
|
|
380
|
+
var FieldRenderer = ({ field, value, onChange, readOnly, error }) => {
|
|
381
|
+
const baseInputClass = "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50";
|
|
382
|
+
const renderInput = () => {
|
|
383
|
+
switch (field.type) {
|
|
384
|
+
case "text":
|
|
385
|
+
case "email":
|
|
386
|
+
case "phone":
|
|
387
|
+
case "number":
|
|
388
|
+
case "date":
|
|
389
|
+
case "file":
|
|
390
|
+
return /* @__PURE__ */ jsx(
|
|
391
|
+
"input",
|
|
392
|
+
{
|
|
393
|
+
type: field.type === "phone" ? "tel" : field.type,
|
|
394
|
+
id: field.id,
|
|
395
|
+
placeholder: field.placeholder,
|
|
396
|
+
className: baseInputClass,
|
|
397
|
+
value: value || "",
|
|
398
|
+
onChange: (e) => onChange?.(e.target.value),
|
|
399
|
+
disabled: readOnly
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
case "textarea":
|
|
403
|
+
return /* @__PURE__ */ jsx(
|
|
404
|
+
"textarea",
|
|
405
|
+
{
|
|
406
|
+
id: field.id,
|
|
407
|
+
placeholder: field.placeholder,
|
|
408
|
+
className: clsx(baseInputClass, "min-h-[80px]"),
|
|
409
|
+
value: value || "",
|
|
410
|
+
onChange: (e) => onChange?.(e.target.value),
|
|
411
|
+
disabled: readOnly
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
case "select":
|
|
415
|
+
return /* @__PURE__ */ jsxs(
|
|
416
|
+
"select",
|
|
417
|
+
{
|
|
418
|
+
id: field.id,
|
|
419
|
+
className: baseInputClass,
|
|
420
|
+
value: value || "",
|
|
421
|
+
onChange: (e) => onChange?.(e.target.value),
|
|
422
|
+
disabled: readOnly,
|
|
423
|
+
children: [
|
|
424
|
+
/* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select an option" }),
|
|
425
|
+
field.options?.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
case "checkbox":
|
|
430
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center h-10", children: /* @__PURE__ */ jsx(
|
|
431
|
+
"input",
|
|
432
|
+
{
|
|
433
|
+
type: "checkbox",
|
|
434
|
+
id: field.id,
|
|
435
|
+
className: "h-5 w-5 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer",
|
|
436
|
+
checked: !!value,
|
|
437
|
+
onChange: (e) => onChange?.(e.target.checked),
|
|
438
|
+
disabled: readOnly
|
|
439
|
+
}
|
|
440
|
+
) });
|
|
441
|
+
case "radio":
|
|
442
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: field.options?.map((opt) => /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
443
|
+
/* @__PURE__ */ jsx(
|
|
444
|
+
"input",
|
|
445
|
+
{
|
|
446
|
+
type: "radio",
|
|
447
|
+
id: `${field.id}-${opt.value}`,
|
|
448
|
+
name: field.id,
|
|
449
|
+
value: opt.value,
|
|
450
|
+
checked: value === opt.value,
|
|
451
|
+
onChange: (e) => onChange?.(e.target.value),
|
|
452
|
+
disabled: readOnly,
|
|
453
|
+
className: "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
454
|
+
}
|
|
455
|
+
),
|
|
456
|
+
/* @__PURE__ */ jsx("label", { htmlFor: `${field.id}-${opt.value}`, className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: opt.label })
|
|
457
|
+
] }, opt.value)) });
|
|
458
|
+
case "toggle":
|
|
459
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center space-x-2", children: /* @__PURE__ */ jsx(
|
|
460
|
+
"button",
|
|
461
|
+
{
|
|
462
|
+
type: "button",
|
|
463
|
+
role: "switch",
|
|
464
|
+
"aria-checked": !!value,
|
|
465
|
+
onClick: () => !readOnly && onChange?.(!value),
|
|
466
|
+
className: clsx(
|
|
467
|
+
"peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
468
|
+
value ? "bg-primary" : "bg-input"
|
|
469
|
+
),
|
|
470
|
+
disabled: readOnly,
|
|
471
|
+
children: /* @__PURE__ */ jsx(
|
|
472
|
+
"span",
|
|
473
|
+
{
|
|
474
|
+
className: clsx(
|
|
475
|
+
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform",
|
|
476
|
+
value ? "translate-x-5" : "translate-x-0"
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
) });
|
|
482
|
+
default:
|
|
483
|
+
return /* @__PURE__ */ jsxs("div", { className: "text-red-500", children: [
|
|
484
|
+
"Unknown field type: ",
|
|
485
|
+
field.type
|
|
486
|
+
] });
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
|
|
490
|
+
/* @__PURE__ */ jsxs("label", { htmlFor: field.id, className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 mb-2 block text-gray-900 dark:text-gray-100", children: [
|
|
491
|
+
field.label,
|
|
492
|
+
field.required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-1", children: "*" })
|
|
493
|
+
] }),
|
|
494
|
+
renderInput(),
|
|
495
|
+
field.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: field.description }),
|
|
496
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive mt-1", children: error })
|
|
497
|
+
] });
|
|
498
|
+
};
|
|
499
|
+
var SortableField = ({ field }) => {
|
|
500
|
+
const {
|
|
501
|
+
attributes,
|
|
502
|
+
listeners,
|
|
503
|
+
setNodeRef,
|
|
504
|
+
transform,
|
|
505
|
+
transition,
|
|
506
|
+
isDragging
|
|
507
|
+
} = useSortable({
|
|
508
|
+
id: field.id,
|
|
509
|
+
data: {
|
|
510
|
+
type: "field",
|
|
511
|
+
field
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
const { selectField, selectedFieldId, removeField } = useFormStore();
|
|
515
|
+
const isSelected = selectedFieldId === field.id;
|
|
516
|
+
const style = {
|
|
517
|
+
transform: CSS.Transform.toString(transform),
|
|
518
|
+
transition,
|
|
519
|
+
gridColumn: field.width === "100%" ? "span 4" : field.width === "50%" ? "span 2" : "span 1"
|
|
520
|
+
};
|
|
521
|
+
return /* @__PURE__ */ jsxs(
|
|
522
|
+
"div",
|
|
523
|
+
{
|
|
524
|
+
ref: setNodeRef,
|
|
525
|
+
style,
|
|
526
|
+
className: clsx(
|
|
527
|
+
"relative group rounded-lg border-2 transition-all bg-white dark:bg-gray-800",
|
|
528
|
+
isSelected ? "border-blue-500 ring-2 ring-blue-200" : "border-transparent hover:border-gray-300 dark:hover:border-gray-600",
|
|
529
|
+
isDragging && "opacity-50 z-50"
|
|
530
|
+
),
|
|
531
|
+
onClick: (e) => {
|
|
532
|
+
e.stopPropagation();
|
|
533
|
+
selectField(field.id);
|
|
534
|
+
},
|
|
535
|
+
children: [
|
|
536
|
+
/* @__PURE__ */ jsx(
|
|
537
|
+
"div",
|
|
538
|
+
{
|
|
539
|
+
...attributes,
|
|
540
|
+
...listeners,
|
|
541
|
+
className: clsx(
|
|
542
|
+
"absolute top-2 left-2 cursor-move p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
543
|
+
isSelected && "opacity-100"
|
|
544
|
+
),
|
|
545
|
+
children: /* @__PURE__ */ jsx(GripVertical, { size: 16 })
|
|
546
|
+
}
|
|
547
|
+
),
|
|
548
|
+
/* @__PURE__ */ jsx(
|
|
549
|
+
"button",
|
|
550
|
+
{
|
|
551
|
+
onClick: (e) => {
|
|
552
|
+
e.stopPropagation();
|
|
553
|
+
removeField(field.id);
|
|
554
|
+
},
|
|
555
|
+
className: clsx(
|
|
556
|
+
"absolute top-2 right-2 p-1 rounded hover:bg-red-50 text-red-400 hover:text-red-600 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
557
|
+
isSelected && "opacity-100"
|
|
558
|
+
),
|
|
559
|
+
children: /* @__PURE__ */ jsx(Trash2, { size: 16 })
|
|
560
|
+
}
|
|
561
|
+
),
|
|
562
|
+
/* @__PURE__ */ jsx("div", { className: "p-4 pointer-events-none", children: /* @__PURE__ */ jsx(FieldRenderer, { field, readOnly: true }) })
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
};
|
|
567
|
+
var SortableSection = ({ section }) => {
|
|
568
|
+
const {
|
|
569
|
+
attributes,
|
|
570
|
+
listeners,
|
|
571
|
+
setNodeRef,
|
|
572
|
+
transform,
|
|
573
|
+
transition,
|
|
574
|
+
isDragging
|
|
575
|
+
} = useSortable({
|
|
576
|
+
id: section.id,
|
|
577
|
+
data: {
|
|
578
|
+
type: "section",
|
|
579
|
+
section
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
const { removeSection, updateSection } = useFormStore();
|
|
583
|
+
const { setNodeRef: setDroppableNodeRef } = useDroppable({
|
|
584
|
+
id: section.id,
|
|
585
|
+
data: {
|
|
586
|
+
type: "section",
|
|
587
|
+
section
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
const style = {
|
|
591
|
+
transform: CSS.Transform.toString(transform),
|
|
592
|
+
transition
|
|
593
|
+
};
|
|
594
|
+
return /* @__PURE__ */ jsxs(
|
|
595
|
+
"div",
|
|
596
|
+
{
|
|
597
|
+
ref: setNodeRef,
|
|
598
|
+
style,
|
|
599
|
+
className: clsx(
|
|
600
|
+
"mb-6 rounded-lg border bg-white dark:bg-gray-900 shadow-sm transition-all",
|
|
601
|
+
isDragging ? "opacity-50 z-50 border-blue-500" : "border-gray-200 dark:border-gray-800"
|
|
602
|
+
),
|
|
603
|
+
children: [
|
|
604
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-4 border-b border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800/50 rounded-t-lg", children: [
|
|
605
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center flex-1", children: [
|
|
606
|
+
/* @__PURE__ */ jsx(
|
|
607
|
+
"div",
|
|
608
|
+
{
|
|
609
|
+
...attributes,
|
|
610
|
+
...listeners,
|
|
611
|
+
className: "cursor-move mr-3 text-gray-400 hover:text-gray-600",
|
|
612
|
+
children: /* @__PURE__ */ jsx(GripVertical, { size: 20 })
|
|
613
|
+
}
|
|
614
|
+
),
|
|
615
|
+
/* @__PURE__ */ jsx(
|
|
616
|
+
"input",
|
|
617
|
+
{
|
|
618
|
+
value: section.title,
|
|
619
|
+
onChange: (e) => updateSection(section.id, { title: e.target.value }),
|
|
620
|
+
className: "bg-transparent font-semibold text-gray-700 dark:text-gray-200 focus:outline-none focus:border-b border-blue-500",
|
|
621
|
+
placeholder: "Section Title"
|
|
622
|
+
}
|
|
623
|
+
)
|
|
624
|
+
] }),
|
|
625
|
+
/* @__PURE__ */ jsx(
|
|
626
|
+
"button",
|
|
627
|
+
{
|
|
628
|
+
onClick: () => removeSection(section.id),
|
|
629
|
+
className: "text-gray-400 hover:text-red-500 transition-colors p-1",
|
|
630
|
+
children: /* @__PURE__ */ jsx(Trash2, { size: 18 })
|
|
631
|
+
}
|
|
632
|
+
)
|
|
633
|
+
] }),
|
|
634
|
+
/* @__PURE__ */ jsxs(
|
|
635
|
+
"div",
|
|
636
|
+
{
|
|
637
|
+
ref: setDroppableNodeRef,
|
|
638
|
+
className: "p-4 min-h-[100px] grid grid-cols-4 gap-4",
|
|
639
|
+
children: [
|
|
640
|
+
/* @__PURE__ */ jsx(
|
|
641
|
+
SortableContext,
|
|
642
|
+
{
|
|
643
|
+
items: section.fields.map((f) => f.id),
|
|
644
|
+
strategy: rectSortingStrategy,
|
|
645
|
+
children: section.fields.map((field) => /* @__PURE__ */ jsx(SortableField, { field }, field.id))
|
|
646
|
+
}
|
|
647
|
+
),
|
|
648
|
+
section.fields.length === 0 && /* @__PURE__ */ jsx("div", { className: "col-span-4 flex flex-col items-center justify-center py-8 border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg text-gray-400", children: /* @__PURE__ */ jsx("p", { className: "text-sm", children: "Drop fields here" }) })
|
|
649
|
+
]
|
|
650
|
+
}
|
|
651
|
+
)
|
|
652
|
+
]
|
|
653
|
+
}
|
|
654
|
+
);
|
|
655
|
+
};
|
|
656
|
+
var Canvas = () => {
|
|
657
|
+
const { schema, addSection, selectField } = useFormStore();
|
|
658
|
+
const { setNodeRef } = useDroppable({
|
|
659
|
+
id: "canvas",
|
|
660
|
+
data: {
|
|
661
|
+
type: "canvas"
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
return /* @__PURE__ */ jsx(
|
|
665
|
+
"div",
|
|
666
|
+
{
|
|
667
|
+
className: "flex-1 bg-gray-100 dark:bg-gray-950 p-8 overflow-y-auto h-full",
|
|
668
|
+
onClick: () => selectField(null),
|
|
669
|
+
children: /* @__PURE__ */ jsxs("div", { className: "max-w-3xl mx-auto", children: [
|
|
670
|
+
/* @__PURE__ */ jsx("div", { className: "mb-8 text-center", children: /* @__PURE__ */ jsx(
|
|
671
|
+
"input",
|
|
672
|
+
{
|
|
673
|
+
value: schema.title,
|
|
674
|
+
onChange: (e) => useFormStore.getState().setSchema({ ...schema, title: e.target.value }),
|
|
675
|
+
className: "text-3xl font-bold text-center bg-transparent border-none focus:outline-none focus:ring-0 w-full text-gray-900 dark:text-white",
|
|
676
|
+
placeholder: "Form Title"
|
|
677
|
+
}
|
|
678
|
+
) }),
|
|
679
|
+
/* @__PURE__ */ jsx("div", { ref: setNodeRef, className: "space-y-6 min-h-[200px]", children: /* @__PURE__ */ jsx(
|
|
680
|
+
SortableContext,
|
|
681
|
+
{
|
|
682
|
+
items: schema.sections.map((s) => s.id),
|
|
683
|
+
strategy: verticalListSortingStrategy,
|
|
684
|
+
children: schema.sections.map((section) => /* @__PURE__ */ jsx(SortableSection, { section }, section.id))
|
|
685
|
+
}
|
|
686
|
+
) }),
|
|
687
|
+
/* @__PURE__ */ jsxs(
|
|
688
|
+
"button",
|
|
689
|
+
{
|
|
690
|
+
onClick: addSection,
|
|
691
|
+
className: "w-full mt-6 py-4 border-2 border-dashed border-gray-300 dark:border-gray-700 rounded-lg text-gray-500 hover:border-blue-500 hover:text-blue-500 transition-colors flex items-center justify-center font-medium",
|
|
692
|
+
children: [
|
|
693
|
+
/* @__PURE__ */ jsx(Plus, { size: 20, className: "mr-2" }),
|
|
694
|
+
"Add Section"
|
|
695
|
+
]
|
|
696
|
+
}
|
|
697
|
+
)
|
|
698
|
+
] })
|
|
699
|
+
}
|
|
700
|
+
);
|
|
701
|
+
};
|
|
702
|
+
var FieldConfigPanel = () => {
|
|
703
|
+
const { schema, selectedFieldId, updateField, selectField } = useFormStore();
|
|
704
|
+
const selectedField = React.useMemo(() => {
|
|
705
|
+
if (!selectedFieldId)
|
|
706
|
+
return null;
|
|
707
|
+
for (const section of schema.sections) {
|
|
708
|
+
const field = section.fields.find((f) => f.id === selectedFieldId);
|
|
709
|
+
if (field)
|
|
710
|
+
return field;
|
|
711
|
+
}
|
|
712
|
+
return null;
|
|
713
|
+
}, [schema, selectedFieldId]);
|
|
714
|
+
if (!selectedField) {
|
|
715
|
+
return /* @__PURE__ */ jsx("div", { className: "w-80 bg-white dark:bg-gray-900 border-l border-gray-200 dark:border-gray-800 p-6 flex flex-col items-center justify-center text-center text-gray-500", children: /* @__PURE__ */ jsx("p", { children: "Select a field to configure its properties" }) });
|
|
716
|
+
}
|
|
717
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-80 bg-white dark:bg-gray-900 border-l border-gray-200 dark:border-gray-800 flex flex-col h-full", children: [
|
|
718
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800", children: [
|
|
719
|
+
/* @__PURE__ */ jsx("h2", { className: "font-semibold text-gray-900 dark:text-white", children: "Field Settings" }),
|
|
720
|
+
/* @__PURE__ */ jsx("button", { onClick: () => selectField(null), className: "text-gray-500 hover:text-gray-700", children: /* @__PURE__ */ jsx(X, { size: 20 }) })
|
|
721
|
+
] }),
|
|
722
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-6", children: [
|
|
723
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
724
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
725
|
+
/* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: "Label" }),
|
|
726
|
+
/* @__PURE__ */ jsx(
|
|
727
|
+
"input",
|
|
728
|
+
{
|
|
729
|
+
type: "text",
|
|
730
|
+
value: selectedField.label,
|
|
731
|
+
onChange: (e) => updateField(selectedField.id, { label: e.target.value }),
|
|
732
|
+
className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent"
|
|
733
|
+
}
|
|
734
|
+
)
|
|
735
|
+
] }),
|
|
736
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
737
|
+
/* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: "Placeholder" }),
|
|
738
|
+
/* @__PURE__ */ jsx(
|
|
739
|
+
"input",
|
|
740
|
+
{
|
|
741
|
+
type: "text",
|
|
742
|
+
value: selectedField.placeholder || "",
|
|
743
|
+
onChange: (e) => updateField(selectedField.id, { placeholder: e.target.value }),
|
|
744
|
+
className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent"
|
|
745
|
+
}
|
|
746
|
+
)
|
|
747
|
+
] }),
|
|
748
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
749
|
+
/* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: "Description" }),
|
|
750
|
+
/* @__PURE__ */ jsx(
|
|
751
|
+
"textarea",
|
|
752
|
+
{
|
|
753
|
+
value: selectedField.description || "",
|
|
754
|
+
onChange: (e) => updateField(selectedField.id, { description: e.target.value }),
|
|
755
|
+
className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent",
|
|
756
|
+
rows: 2
|
|
757
|
+
}
|
|
758
|
+
)
|
|
759
|
+
] })
|
|
760
|
+
] }),
|
|
761
|
+
/* @__PURE__ */ jsx("hr", { className: "border-gray-200 dark:border-gray-800" }),
|
|
762
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
763
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Layout" }),
|
|
764
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
765
|
+
/* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: "Width" }),
|
|
766
|
+
/* @__PURE__ */ jsxs(
|
|
767
|
+
"select",
|
|
768
|
+
{
|
|
769
|
+
value: selectedField.width,
|
|
770
|
+
onChange: (e) => updateField(selectedField.id, { width: e.target.value }),
|
|
771
|
+
className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent",
|
|
772
|
+
children: [
|
|
773
|
+
/* @__PURE__ */ jsx("option", { value: "25%", children: "25% (1/4)" }),
|
|
774
|
+
/* @__PURE__ */ jsx("option", { value: "50%", children: "50% (1/2)" }),
|
|
775
|
+
/* @__PURE__ */ jsx("option", { value: "100%", children: "100% (Full)" })
|
|
776
|
+
]
|
|
777
|
+
}
|
|
778
|
+
)
|
|
779
|
+
] })
|
|
780
|
+
] }),
|
|
781
|
+
/* @__PURE__ */ jsx("hr", { className: "border-gray-200 dark:border-gray-800" }),
|
|
782
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
783
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Validation" }),
|
|
784
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
785
|
+
/* @__PURE__ */ jsx("label", { className: "text-sm text-gray-700 dark:text-gray-300", children: "Required" }),
|
|
786
|
+
/* @__PURE__ */ jsx(
|
|
787
|
+
"input",
|
|
788
|
+
{
|
|
789
|
+
type: "checkbox",
|
|
790
|
+
checked: selectedField.required || false,
|
|
791
|
+
onChange: (e) => updateField(selectedField.id, { required: e.target.checked }),
|
|
792
|
+
className: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
793
|
+
}
|
|
794
|
+
)
|
|
795
|
+
] })
|
|
796
|
+
] }),
|
|
797
|
+
(selectedField.type === "select" || selectedField.type === "radio") && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
798
|
+
/* @__PURE__ */ jsx("hr", { className: "border-gray-200 dark:border-gray-800" }),
|
|
799
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
800
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Options" }),
|
|
801
|
+
selectedField.options?.map((option, index) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
802
|
+
/* @__PURE__ */ jsx(
|
|
803
|
+
"input",
|
|
804
|
+
{
|
|
805
|
+
placeholder: "Label",
|
|
806
|
+
value: option.label,
|
|
807
|
+
onChange: (e) => {
|
|
808
|
+
const newOptions = [...selectedField.options || []];
|
|
809
|
+
newOptions[index].label = e.target.value;
|
|
810
|
+
updateField(selectedField.id, { options: newOptions });
|
|
811
|
+
},
|
|
812
|
+
className: "flex-1 px-2 py-1 border border-gray-300 dark:border-gray-700 rounded text-sm"
|
|
813
|
+
}
|
|
814
|
+
),
|
|
815
|
+
/* @__PURE__ */ jsx(
|
|
816
|
+
"input",
|
|
817
|
+
{
|
|
818
|
+
placeholder: "Value",
|
|
819
|
+
value: option.value,
|
|
820
|
+
onChange: (e) => {
|
|
821
|
+
const newOptions = [...selectedField.options || []];
|
|
822
|
+
newOptions[index].value = e.target.value;
|
|
823
|
+
updateField(selectedField.id, { options: newOptions });
|
|
824
|
+
},
|
|
825
|
+
className: "flex-1 px-2 py-1 border border-gray-300 dark:border-gray-700 rounded text-sm"
|
|
826
|
+
}
|
|
827
|
+
),
|
|
828
|
+
/* @__PURE__ */ jsx(
|
|
829
|
+
"button",
|
|
830
|
+
{
|
|
831
|
+
onClick: () => {
|
|
832
|
+
const newOptions = selectedField.options?.filter((_, i) => i !== index);
|
|
833
|
+
updateField(selectedField.id, { options: newOptions });
|
|
834
|
+
},
|
|
835
|
+
className: "text-red-500 hover:text-red-700",
|
|
836
|
+
children: /* @__PURE__ */ jsx(X, { size: 16 })
|
|
837
|
+
}
|
|
838
|
+
)
|
|
839
|
+
] }, index)),
|
|
840
|
+
/* @__PURE__ */ jsx(
|
|
841
|
+
"button",
|
|
842
|
+
{
|
|
843
|
+
onClick: () => {
|
|
844
|
+
const newOptions = [...selectedField.options || [], { label: "New Option", value: "new_option" }];
|
|
845
|
+
updateField(selectedField.id, { options: newOptions });
|
|
846
|
+
},
|
|
847
|
+
className: "text-sm text-blue-600 hover:text-blue-700 font-medium",
|
|
848
|
+
children: "+ Add Option"
|
|
849
|
+
}
|
|
850
|
+
)
|
|
851
|
+
] })
|
|
852
|
+
] })
|
|
853
|
+
] })
|
|
854
|
+
] });
|
|
855
|
+
};
|
|
856
|
+
var FormRenderer = ({ schema, onSubmit, className }) => {
|
|
857
|
+
const [formData, setFormData] = useState({});
|
|
858
|
+
const [errors, setErrors] = useState({});
|
|
859
|
+
const handleChange = (fieldId, value) => {
|
|
860
|
+
setFormData((prev) => ({ ...prev, [fieldId]: value }));
|
|
861
|
+
if (errors[fieldId]) {
|
|
862
|
+
setErrors((prev) => {
|
|
863
|
+
const newErrors = { ...prev };
|
|
864
|
+
delete newErrors[fieldId];
|
|
865
|
+
return newErrors;
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
const validate = () => {
|
|
870
|
+
const newErrors = {};
|
|
871
|
+
let isValid = true;
|
|
872
|
+
schema.sections.forEach((section) => {
|
|
873
|
+
section.fields.forEach((field) => {
|
|
874
|
+
const value = formData[field.id];
|
|
875
|
+
if (field.required && !value) {
|
|
876
|
+
newErrors[field.id] = "This field is required";
|
|
877
|
+
isValid = false;
|
|
878
|
+
}
|
|
879
|
+
if (field.validation) {
|
|
880
|
+
field.validation.forEach((rule) => {
|
|
881
|
+
if (rule.type === "email" && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
882
|
+
newErrors[field.id] = rule.message || "Invalid email address";
|
|
883
|
+
isValid = false;
|
|
884
|
+
}
|
|
885
|
+
if (rule.type === "min" && typeof value === "number" && value < rule.value) {
|
|
886
|
+
newErrors[field.id] = rule.message || `Minimum value is ${rule.value}`;
|
|
887
|
+
isValid = false;
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
});
|
|
893
|
+
setErrors(newErrors);
|
|
894
|
+
return isValid;
|
|
895
|
+
};
|
|
896
|
+
const handleSubmit = (e) => {
|
|
897
|
+
e.preventDefault();
|
|
898
|
+
if (validate()) {
|
|
899
|
+
onSubmit?.(formData);
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: clsx("space-y-8", className), children: [
|
|
903
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-gray-900 dark:text-white", children: schema.title }) }),
|
|
904
|
+
schema.sections.map((section) => /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
905
|
+
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-gray-800 dark:text-gray-200 border-b pb-2", children: section.title }),
|
|
906
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 gap-4", children: section.fields.map((field) => /* @__PURE__ */ jsx(
|
|
907
|
+
"div",
|
|
908
|
+
{
|
|
909
|
+
style: {
|
|
910
|
+
gridColumn: field.width === "100%" ? "span 4" : field.width === "50%" ? "span 2" : "span 1"
|
|
911
|
+
},
|
|
912
|
+
children: /* @__PURE__ */ jsx(
|
|
913
|
+
FieldRenderer,
|
|
914
|
+
{
|
|
915
|
+
field,
|
|
916
|
+
value: formData[field.id],
|
|
917
|
+
onChange: (val) => handleChange(field.id, val),
|
|
918
|
+
error: errors[field.id]
|
|
919
|
+
}
|
|
920
|
+
)
|
|
921
|
+
},
|
|
922
|
+
field.id
|
|
923
|
+
)) })
|
|
924
|
+
] }, section.id)),
|
|
925
|
+
/* @__PURE__ */ jsx("div", { className: "pt-4", children: /* @__PURE__ */ jsx(
|
|
926
|
+
"button",
|
|
927
|
+
{
|
|
928
|
+
type: "submit",
|
|
929
|
+
className: "px-6 py-2 bg-blue-600 text-white font-medium rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors",
|
|
930
|
+
children: "Submit"
|
|
931
|
+
}
|
|
932
|
+
) })
|
|
933
|
+
] });
|
|
934
|
+
};
|
|
935
|
+
var dropAnimation = {
|
|
936
|
+
sideEffects: defaultDropAnimationSideEffects({
|
|
937
|
+
styles: {
|
|
938
|
+
active: {
|
|
939
|
+
opacity: "0.5"
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
})
|
|
943
|
+
};
|
|
944
|
+
var FormBuilder = () => {
|
|
945
|
+
const {
|
|
946
|
+
schema,
|
|
947
|
+
addField,
|
|
948
|
+
moveField,
|
|
949
|
+
moveSection,
|
|
950
|
+
isPreviewMode
|
|
951
|
+
} = useFormStore();
|
|
952
|
+
const [activeId, setActiveId] = useState(null);
|
|
953
|
+
const [activeData, setActiveData] = useState(null);
|
|
954
|
+
const sensors = useSensors(
|
|
955
|
+
useSensor(PointerSensor, {
|
|
956
|
+
activationConstraint: {
|
|
957
|
+
distance: 5
|
|
958
|
+
}
|
|
959
|
+
}),
|
|
960
|
+
useSensor(KeyboardSensor, {
|
|
961
|
+
coordinateGetter: sortableKeyboardCoordinates
|
|
962
|
+
})
|
|
963
|
+
);
|
|
964
|
+
const handleDragStart = (event) => {
|
|
965
|
+
setActiveId(event.active.id);
|
|
966
|
+
setActiveData(event.active.data.current);
|
|
967
|
+
};
|
|
968
|
+
const handleDragOver = (event) => {
|
|
969
|
+
const { active, over } = event;
|
|
970
|
+
if (!over)
|
|
971
|
+
return;
|
|
972
|
+
const activeType = active.data.current?.type;
|
|
973
|
+
const overType = over.data.current?.type;
|
|
974
|
+
if (activeType === "toolbox-item" && overType === "section") {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
const handleDragEnd = (event) => {
|
|
979
|
+
const { active, over } = event;
|
|
980
|
+
setActiveId(null);
|
|
981
|
+
setActiveData(null);
|
|
982
|
+
if (!over)
|
|
983
|
+
return;
|
|
984
|
+
const activeType = active.data.current?.type;
|
|
985
|
+
const overType = over.data.current?.type;
|
|
986
|
+
if (activeType === "toolbox-item") {
|
|
987
|
+
const fieldType = active.data.current?.fieldType;
|
|
988
|
+
if (overType === "section") {
|
|
989
|
+
addField(over.id, fieldType);
|
|
990
|
+
} else if (overType === "field") {
|
|
991
|
+
const section = schema.sections.find((s) => s.fields.some((f) => f.id === over.id));
|
|
992
|
+
if (section) {
|
|
993
|
+
const index = section.fields.findIndex((f) => f.id === over.id);
|
|
994
|
+
addField(section.id, fieldType, index + 1);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
if (activeType === "section" && overType === "section") {
|
|
1000
|
+
if (active.id !== over.id) {
|
|
1001
|
+
moveSection(active.id, over.id);
|
|
1002
|
+
}
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
if (activeType === "field") {
|
|
1006
|
+
const activeFieldId = active.id;
|
|
1007
|
+
const overId = over.id;
|
|
1008
|
+
const activeSection = schema.sections.find((s) => s.fields.some((f) => f.id === activeFieldId));
|
|
1009
|
+
let overSection;
|
|
1010
|
+
if (overType === "section") {
|
|
1011
|
+
overSection = schema.sections.find((s) => s.id === overId);
|
|
1012
|
+
} else if (overType === "field") {
|
|
1013
|
+
overSection = schema.sections.find((s) => s.fields.some((f) => f.id === overId));
|
|
1014
|
+
}
|
|
1015
|
+
if (activeSection && overSection) {
|
|
1016
|
+
moveField(activeFieldId, overId, activeSection.id, overSection.id);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
return /* @__PURE__ */ jsxs(
|
|
1021
|
+
DndContext,
|
|
1022
|
+
{
|
|
1023
|
+
sensors,
|
|
1024
|
+
collisionDetection: closestCorners,
|
|
1025
|
+
onDragStart: handleDragStart,
|
|
1026
|
+
onDragOver: handleDragOver,
|
|
1027
|
+
onDragEnd: handleDragEnd,
|
|
1028
|
+
children: [
|
|
1029
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col h-screen bg-gray-100 dark:bg-gray-950", children: [
|
|
1030
|
+
/* @__PURE__ */ jsx(Toolbar, {}),
|
|
1031
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-1 overflow-hidden", children: isPreviewMode ? /* @__PURE__ */ jsx("div", { className: "flex-1 p-8 overflow-y-auto bg-white dark:bg-gray-900 flex justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-full max-w-3xl", children: /* @__PURE__ */ jsx(FormRenderer, { schema, onSubmit: (data) => alert(JSON.stringify(data, null, 2)) }) }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1032
|
+
/* @__PURE__ */ jsx(FieldToolbox, {}),
|
|
1033
|
+
/* @__PURE__ */ jsx(Canvas, {}),
|
|
1034
|
+
/* @__PURE__ */ jsx(FieldConfigPanel, {})
|
|
1035
|
+
] }) })
|
|
1036
|
+
] }),
|
|
1037
|
+
createPortal(
|
|
1038
|
+
/* @__PURE__ */ jsxs(DragOverlay, { dropAnimation, children: [
|
|
1039
|
+
activeId && activeData?.type === "toolbox-item" && /* @__PURE__ */ jsx("div", { className: "p-3 bg-white border border-blue-500 rounded shadow-lg w-48", children: activeData.fieldType }),
|
|
1040
|
+
activeId && activeData?.type === "field" && /* @__PURE__ */ jsx("div", { className: "opacity-80", children: /* @__PURE__ */ jsx(SortableField, { field: activeData.field }) }),
|
|
1041
|
+
activeId && activeData?.type === "section" && /* @__PURE__ */ jsx("div", { className: "opacity-80", children: /* @__PURE__ */ jsx(SortableSection, { section: activeData.section }) })
|
|
1042
|
+
] }),
|
|
1043
|
+
document.body
|
|
1044
|
+
)
|
|
1045
|
+
]
|
|
1046
|
+
}
|
|
1047
|
+
);
|
|
1048
|
+
};
|
|
1049
|
+
var ReactCustomElement = class extends HTMLElement {
|
|
1050
|
+
constructor(Component) {
|
|
1051
|
+
super();
|
|
1052
|
+
__publicField(this, "root", null);
|
|
1053
|
+
__publicField(this, "props", {});
|
|
1054
|
+
__publicField(this, "Component");
|
|
1055
|
+
this.Component = Component;
|
|
1056
|
+
}
|
|
1057
|
+
connectedCallback() {
|
|
1058
|
+
if (!this.root) {
|
|
1059
|
+
this.root = ReactDOM.createRoot(this);
|
|
1060
|
+
this.render();
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
disconnectedCallback() {
|
|
1064
|
+
if (this.root) {
|
|
1065
|
+
this.root.unmount();
|
|
1066
|
+
this.root = null;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1070
|
+
if (oldValue !== newValue) {
|
|
1071
|
+
this.props[name] = newValue;
|
|
1072
|
+
this.render();
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
// Allow setting complex props via JS properties
|
|
1076
|
+
setProp(name, value) {
|
|
1077
|
+
this.props[name] = value;
|
|
1078
|
+
this.render();
|
|
1079
|
+
}
|
|
1080
|
+
render() {
|
|
1081
|
+
if (this.root) {
|
|
1082
|
+
const Component = this.Component;
|
|
1083
|
+
this.root.render(
|
|
1084
|
+
/* @__PURE__ */ jsx(React.StrictMode, { children: /* @__PURE__ */ jsx(Component, { ...this.props }) })
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
var FormBuilderElement = class extends ReactCustomElement {
|
|
1090
|
+
constructor() {
|
|
1091
|
+
super(FormBuilder);
|
|
1092
|
+
}
|
|
1093
|
+
// Expose methods or specific props if needed
|
|
1094
|
+
};
|
|
1095
|
+
var FormRendererElement = class extends ReactCustomElement {
|
|
1096
|
+
constructor() {
|
|
1097
|
+
super(FormRenderer);
|
|
1098
|
+
}
|
|
1099
|
+
// Define setters for complex properties
|
|
1100
|
+
set schema(value) {
|
|
1101
|
+
this.setProp("schema", value);
|
|
1102
|
+
}
|
|
1103
|
+
set onSubmit(value) {
|
|
1104
|
+
this.setProp("onSubmit", value);
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
function registerWebComponents() {
|
|
1108
|
+
if (typeof window !== "undefined") {
|
|
1109
|
+
if (!customElements.get("form-builder-pro")) {
|
|
1110
|
+
customElements.define("form-builder-pro", FormBuilderElement);
|
|
1111
|
+
}
|
|
1112
|
+
if (!customElements.get("form-renderer-pro")) {
|
|
1113
|
+
customElements.define("form-renderer-pro", FormRendererElement);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (typeof window !== "undefined") {
|
|
1118
|
+
window.FormBuilderPro = {
|
|
1119
|
+
register: registerWebComponents
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
export { FormBuilder, FormRenderer, FormSchemaValidation, registerWebComponents, useFormStore };
|
|
1124
|
+
//# sourceMappingURL=out.js.map
|
|
1125
|
+
//# sourceMappingURL=index.mjs.map
|