@forms.expert/sdk 0.1.0
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 +331 -0
- package/dist/core/index.cjs +365 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +322 -0
- package/dist/core/index.d.ts +322 -0
- package/dist/core/index.js +334 -0
- package/dist/core/index.js.map +1 -0
- package/dist/react/index.cjs +1014 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +430 -0
- package/dist/react/index.d.ts +430 -0
- package/dist/react/index.js +991 -0
- package/dist/react/index.js.map +1 -0
- package/dist/vanilla/index.cjs +209 -0
- package/dist/vanilla/index.cjs.map +1 -0
- package/dist/vanilla/index.d.cts +188 -0
- package/dist/vanilla/index.d.ts +188 -0
- package/dist/vanilla/index.global.js +209 -0
- package/dist/vanilla/index.global.js.map +1 -0
- package/dist/vanilla/index.js +209 -0
- package/dist/vanilla/index.js.map +1 -0
- package/dist/vue/index.cjs +482 -0
- package/dist/vue/index.cjs.map +1 -0
- package/dist/vue/index.d.cts +197 -0
- package/dist/vue/index.d.ts +197 -0
- package/dist/vue/index.js +453 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,991 @@
|
|
|
1
|
+
// react/use-form.tsx
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
5
|
+
useState,
|
|
6
|
+
useCallback,
|
|
7
|
+
useMemo,
|
|
8
|
+
useEffect
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
// core/types.ts
|
|
12
|
+
var FormsError = class extends Error {
|
|
13
|
+
constructor(message, code, statusCode, retryAfter) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.statusCode = statusCode;
|
|
17
|
+
this.retryAfter = retryAfter;
|
|
18
|
+
this.name = "FormsError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var FormValidationError = class extends Error {
|
|
22
|
+
constructor(errors) {
|
|
23
|
+
super("Validation failed");
|
|
24
|
+
this.errors = errors;
|
|
25
|
+
this.name = "FormValidationError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// core/api-client.ts
|
|
30
|
+
var FormsApiClient = class {
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.apiKey = config.apiKey;
|
|
33
|
+
this.resourceId = config.resourceId;
|
|
34
|
+
this.baseUrl = (config.baseUrl || "https://api.formsapp.io/api/v1").replace(/\/$/, "");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build URL with token query parameter
|
|
38
|
+
*/
|
|
39
|
+
buildUrl(path) {
|
|
40
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
41
|
+
return `${this.baseUrl}${path}${separator}token=${encodeURIComponent(this.apiKey)}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Make an API request
|
|
45
|
+
*/
|
|
46
|
+
async request(method, path, body) {
|
|
47
|
+
const url = this.buildUrl(path);
|
|
48
|
+
const response = await fetch(url, {
|
|
49
|
+
method,
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/json"
|
|
52
|
+
},
|
|
53
|
+
body: body ? JSON.stringify(body) : void 0
|
|
54
|
+
});
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
throw new FormsError(
|
|
58
|
+
data.message || "Request failed",
|
|
59
|
+
data.code || "UNKNOWN_ERROR",
|
|
60
|
+
response.status,
|
|
61
|
+
data.retryAfter
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if form is active and get configuration
|
|
68
|
+
*/
|
|
69
|
+
async isActive(slug) {
|
|
70
|
+
return this.request("GET", `/f/${this.resourceId}/${slug}/is-active`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validate form data without submitting
|
|
74
|
+
*/
|
|
75
|
+
async validate(slug, data) {
|
|
76
|
+
return this.request("POST", `/f/${this.resourceId}/${slug}/validate`, {
|
|
77
|
+
data
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Submit form data (supports files)
|
|
82
|
+
*/
|
|
83
|
+
async submit(slug, data, options) {
|
|
84
|
+
const url = this.buildUrl(`/f/${this.resourceId}/${slug}`);
|
|
85
|
+
const hasFiles = Object.values(data).some(
|
|
86
|
+
(v) => v instanceof File || v instanceof FileList && v.length > 0
|
|
87
|
+
);
|
|
88
|
+
if (hasFiles || options?.onProgress) {
|
|
89
|
+
return this.submitWithFormData(url, data, options);
|
|
90
|
+
}
|
|
91
|
+
return this.request("POST", `/f/${this.resourceId}/${slug}`, {
|
|
92
|
+
data,
|
|
93
|
+
pageUrl: options?.pageUrl || (typeof window !== "undefined" ? window.location.href : void 0),
|
|
94
|
+
captchaToken: options?.captchaToken
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Submit with FormData (for file uploads with progress tracking)
|
|
99
|
+
*/
|
|
100
|
+
submitWithFormData(url, data, options) {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
const formData = new FormData();
|
|
103
|
+
for (const [key, value] of Object.entries(data)) {
|
|
104
|
+
if (value instanceof File) {
|
|
105
|
+
formData.append(key, value);
|
|
106
|
+
} else if (value instanceof FileList) {
|
|
107
|
+
Array.from(value).forEach((file) => formData.append(key, file));
|
|
108
|
+
} else if (value !== void 0 && value !== null) {
|
|
109
|
+
formData.append(`data[${key}]`, String(value));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const pageUrl = options?.pageUrl || (typeof window !== "undefined" ? window.location.href : "");
|
|
113
|
+
if (pageUrl) {
|
|
114
|
+
formData.append("pageUrl", pageUrl);
|
|
115
|
+
}
|
|
116
|
+
if (options?.captchaToken) {
|
|
117
|
+
formData.append("captchaToken", options.captchaToken);
|
|
118
|
+
}
|
|
119
|
+
const xhr = new XMLHttpRequest();
|
|
120
|
+
if (options?.onProgress) {
|
|
121
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
122
|
+
if (event.lengthComputable) {
|
|
123
|
+
options.onProgress({
|
|
124
|
+
loaded: event.loaded,
|
|
125
|
+
total: event.total,
|
|
126
|
+
percentage: Math.round(event.loaded / event.total * 100)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
xhr.addEventListener("load", () => {
|
|
132
|
+
try {
|
|
133
|
+
const response = JSON.parse(xhr.responseText);
|
|
134
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
135
|
+
resolve(response);
|
|
136
|
+
} else {
|
|
137
|
+
reject(new FormsError(
|
|
138
|
+
response.message || "Submission failed",
|
|
139
|
+
response.code || "UNKNOWN_ERROR",
|
|
140
|
+
xhr.status,
|
|
141
|
+
response.retryAfter
|
|
142
|
+
));
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
reject(new FormsError("Invalid response", "PARSE_ERROR", xhr.status));
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
xhr.addEventListener("error", () => {
|
|
149
|
+
reject(new FormsError("Network error", "NETWORK_ERROR", 0));
|
|
150
|
+
});
|
|
151
|
+
xhr.addEventListener("abort", () => {
|
|
152
|
+
reject(new FormsError("Request aborted", "ABORTED", 0));
|
|
153
|
+
});
|
|
154
|
+
xhr.open("POST", url);
|
|
155
|
+
xhr.send(formData);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Track a form view (for analytics completion rate)
|
|
160
|
+
*/
|
|
161
|
+
async trackView(slug) {
|
|
162
|
+
const url = this.buildUrl(`/f/${this.resourceId}/${slug}/view`);
|
|
163
|
+
await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" } }).catch(() => {
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get resource ID
|
|
168
|
+
*/
|
|
169
|
+
getResourceId() {
|
|
170
|
+
return this.resourceId;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get base URL
|
|
174
|
+
*/
|
|
175
|
+
getBaseUrl() {
|
|
176
|
+
return this.baseUrl;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// core/forms-sdk.ts
|
|
181
|
+
var FormHandler = class {
|
|
182
|
+
constructor(apiClient, slug, options = {}) {
|
|
183
|
+
this.config = null;
|
|
184
|
+
this.apiClient = apiClient;
|
|
185
|
+
this.slug = slug;
|
|
186
|
+
this.options = options;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Initialize form handler and fetch configuration
|
|
190
|
+
*/
|
|
191
|
+
async initialize() {
|
|
192
|
+
this.config = await this.apiClient.isActive(this.slug);
|
|
193
|
+
if (this.options.trackViews) {
|
|
194
|
+
this.apiClient.trackView(this.slug);
|
|
195
|
+
}
|
|
196
|
+
return this.config;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get cached form configuration
|
|
200
|
+
*/
|
|
201
|
+
getConfig() {
|
|
202
|
+
return this.config;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check if form is active
|
|
206
|
+
*/
|
|
207
|
+
isActive() {
|
|
208
|
+
return this.config?.active ?? false;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check if captcha is required
|
|
212
|
+
*/
|
|
213
|
+
requiresCaptcha() {
|
|
214
|
+
return this.config?.settings?.captcha?.enabled ?? false;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get captcha provider
|
|
218
|
+
*/
|
|
219
|
+
getCaptchaProvider() {
|
|
220
|
+
return this.config?.settings?.captcha?.provider;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get form schema
|
|
224
|
+
*/
|
|
225
|
+
getSchema() {
|
|
226
|
+
return this.config?.schema;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Validate form data
|
|
230
|
+
*/
|
|
231
|
+
async validate(data) {
|
|
232
|
+
return this.apiClient.validate(this.slug, data);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Submit form data
|
|
236
|
+
*/
|
|
237
|
+
async submit(data, options) {
|
|
238
|
+
this.options.onSubmitStart?.();
|
|
239
|
+
try {
|
|
240
|
+
if (this.config?.mode === "schema") {
|
|
241
|
+
const validation = await this.validate(data);
|
|
242
|
+
if (!validation.valid) {
|
|
243
|
+
this.options.onValidationError?.(validation.errors);
|
|
244
|
+
throw new FormValidationError(validation.errors);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const response = await this.apiClient.submit(this.slug, data, options);
|
|
248
|
+
this.options.onSubmitSuccess?.(response);
|
|
249
|
+
return response;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof FormsError) {
|
|
252
|
+
this.options.onSubmitError?.(error);
|
|
253
|
+
}
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get success message from config
|
|
259
|
+
*/
|
|
260
|
+
getSuccessMessage() {
|
|
261
|
+
return this.config?.settings?.successMessage || "Form submitted successfully!";
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Get redirect URL from config
|
|
265
|
+
*/
|
|
266
|
+
getRedirectUrl() {
|
|
267
|
+
return this.config?.settings?.redirectUrl;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
var FormsSDK = class {
|
|
271
|
+
constructor(config) {
|
|
272
|
+
this.apiClient = new FormsApiClient(config);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Check if form is active and get configuration
|
|
276
|
+
*/
|
|
277
|
+
async isActive(slug) {
|
|
278
|
+
return this.apiClient.isActive(slug);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Validate form data without submitting
|
|
282
|
+
*/
|
|
283
|
+
async validate(slug, data) {
|
|
284
|
+
return this.apiClient.validate(slug, data);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Submit form data
|
|
288
|
+
*/
|
|
289
|
+
async submit(slug, data, options) {
|
|
290
|
+
return this.apiClient.submit(slug, data, options);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Create a form handler for a specific form
|
|
294
|
+
*/
|
|
295
|
+
form(slug, options) {
|
|
296
|
+
return new FormHandler(this.apiClient, slug, options);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Track a form view (for analytics completion rate)
|
|
300
|
+
*/
|
|
301
|
+
async trackView(slug) {
|
|
302
|
+
return this.apiClient.trackView(slug);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Submit with retry logic for rate limits
|
|
306
|
+
*/
|
|
307
|
+
async submitWithRetry(slug, data, options) {
|
|
308
|
+
const maxRetries = options?.maxRetries ?? 3;
|
|
309
|
+
let lastError = null;
|
|
310
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
311
|
+
try {
|
|
312
|
+
return await this.submit(slug, data, options);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
lastError = error;
|
|
315
|
+
if (error instanceof FormsError) {
|
|
316
|
+
if ([
|
|
317
|
+
"VALIDATION_ERROR",
|
|
318
|
+
"CAPTCHA_REQUIRED",
|
|
319
|
+
"ORIGIN_NOT_ALLOWED"
|
|
320
|
+
].includes(error.code)) {
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
if (error.code.includes("RATE_LIMIT")) {
|
|
324
|
+
const retryAfter = error.retryAfter || Math.pow(2, attempt) * 1e3;
|
|
325
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter));
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
await new Promise(
|
|
330
|
+
(resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1e3)
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
throw lastError;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// react/use-form.tsx
|
|
339
|
+
import { jsx } from "react/jsx-runtime";
|
|
340
|
+
var FormsContext = createContext(null);
|
|
341
|
+
function FormsProvider({ config, children }) {
|
|
342
|
+
const sdk = useMemo(() => new FormsSDK(config), [config]);
|
|
343
|
+
const value = useMemo(() => ({ sdk, isReady: true }), [sdk]);
|
|
344
|
+
return /* @__PURE__ */ jsx(FormsContext.Provider, { value, children });
|
|
345
|
+
}
|
|
346
|
+
function formatBytes(bytes) {
|
|
347
|
+
if (bytes === 0) return "0 Bytes";
|
|
348
|
+
const k = 1024;
|
|
349
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
350
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
351
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
352
|
+
}
|
|
353
|
+
function useFormsSDK() {
|
|
354
|
+
const context = useContext(FormsContext);
|
|
355
|
+
if (!context) {
|
|
356
|
+
throw new Error("useFormsSDK must be used within a FormsProvider");
|
|
357
|
+
}
|
|
358
|
+
return context.sdk;
|
|
359
|
+
}
|
|
360
|
+
function useForm(options) {
|
|
361
|
+
const context = useContext(FormsContext);
|
|
362
|
+
const sdk = useMemo(() => {
|
|
363
|
+
if (options.config) {
|
|
364
|
+
return new FormsSDK(options.config);
|
|
365
|
+
}
|
|
366
|
+
if (!context) {
|
|
367
|
+
throw new Error("Either provide config to useForm or use FormsProvider");
|
|
368
|
+
}
|
|
369
|
+
return context.sdk;
|
|
370
|
+
}, [options.config, context]);
|
|
371
|
+
const [config, setConfig] = useState(null);
|
|
372
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
373
|
+
const [isInitializing, setIsInitializing] = useState(false);
|
|
374
|
+
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
375
|
+
const [errors, setErrors] = useState({});
|
|
376
|
+
const [fileErrors, setFileErrors] = useState([]);
|
|
377
|
+
const [values, setValues] = useState({});
|
|
378
|
+
const [error, setError] = useState(null);
|
|
379
|
+
const [uploadProgress, setUploadProgress] = useState(null);
|
|
380
|
+
const initialize = useCallback(async () => {
|
|
381
|
+
setIsInitializing(true);
|
|
382
|
+
try {
|
|
383
|
+
const formConfig = await sdk.isActive(options.slug);
|
|
384
|
+
setConfig(formConfig);
|
|
385
|
+
if (options.trackViews) {
|
|
386
|
+
sdk.trackView(options.slug);
|
|
387
|
+
}
|
|
388
|
+
return formConfig;
|
|
389
|
+
} finally {
|
|
390
|
+
setIsInitializing(false);
|
|
391
|
+
}
|
|
392
|
+
}, [sdk, options.slug, options.trackViews]);
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
if (options.autoInit !== false) {
|
|
395
|
+
initialize();
|
|
396
|
+
}
|
|
397
|
+
}, [initialize, options.autoInit]);
|
|
398
|
+
const setValue = useCallback((name, value) => {
|
|
399
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
400
|
+
setErrors((prev) => {
|
|
401
|
+
if (prev[name]) {
|
|
402
|
+
const { [name]: _, ...rest } = prev;
|
|
403
|
+
return rest;
|
|
404
|
+
}
|
|
405
|
+
return prev;
|
|
406
|
+
});
|
|
407
|
+
}, []);
|
|
408
|
+
const setValuesHandler = useCallback((newValues) => {
|
|
409
|
+
setValues((prev) => ({ ...prev, ...newValues }));
|
|
410
|
+
}, []);
|
|
411
|
+
const validate = useCallback(async () => {
|
|
412
|
+
const result = await sdk.validate(options.slug, values);
|
|
413
|
+
if (!result.valid) {
|
|
414
|
+
const errorMap = result.errors.reduce(
|
|
415
|
+
(acc, err) => ({ ...acc, [err.field]: err.message }),
|
|
416
|
+
{}
|
|
417
|
+
);
|
|
418
|
+
setErrors(errorMap);
|
|
419
|
+
}
|
|
420
|
+
return result.valid;
|
|
421
|
+
}, [sdk, options.slug, values]);
|
|
422
|
+
const submit = useCallback(
|
|
423
|
+
async (captchaToken) => {
|
|
424
|
+
setIsLoading(true);
|
|
425
|
+
setErrors({});
|
|
426
|
+
setFileErrors([]);
|
|
427
|
+
setError(null);
|
|
428
|
+
setUploadProgress(null);
|
|
429
|
+
try {
|
|
430
|
+
const submitData = config?.settings?.honeypot ? { ...values, _hp: "" } : values;
|
|
431
|
+
const response = await sdk.submit(options.slug, submitData, {
|
|
432
|
+
captchaToken,
|
|
433
|
+
onProgress: setUploadProgress
|
|
434
|
+
});
|
|
435
|
+
setIsSubmitted(true);
|
|
436
|
+
options.onSuccess?.(response);
|
|
437
|
+
return response;
|
|
438
|
+
} catch (err) {
|
|
439
|
+
if (err instanceof FormValidationError) {
|
|
440
|
+
const errorMap = err.errors.reduce(
|
|
441
|
+
(acc, e) => ({ ...acc, [e.field]: e.message }),
|
|
442
|
+
{}
|
|
443
|
+
);
|
|
444
|
+
setErrors(errorMap);
|
|
445
|
+
options.onValidationError?.(err.errors);
|
|
446
|
+
} else {
|
|
447
|
+
setError(err);
|
|
448
|
+
options.onError?.(err);
|
|
449
|
+
}
|
|
450
|
+
return null;
|
|
451
|
+
} finally {
|
|
452
|
+
setIsLoading(false);
|
|
453
|
+
setUploadProgress(null);
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
[sdk, options, values]
|
|
457
|
+
);
|
|
458
|
+
const reset = useCallback(() => {
|
|
459
|
+
setValues({});
|
|
460
|
+
setErrors({});
|
|
461
|
+
setFileErrors([]);
|
|
462
|
+
setError(null);
|
|
463
|
+
setIsSubmitted(false);
|
|
464
|
+
setIsLoading(false);
|
|
465
|
+
setUploadProgress(null);
|
|
466
|
+
}, []);
|
|
467
|
+
const clearErrors = useCallback(() => {
|
|
468
|
+
setErrors({});
|
|
469
|
+
setFileErrors([]);
|
|
470
|
+
}, []);
|
|
471
|
+
const validateFiles = useCallback((files) => {
|
|
472
|
+
const errors2 = [];
|
|
473
|
+
const maxSize = config?.settings?.maxAttachmentSize ?? 10 * 1024 * 1024;
|
|
474
|
+
const maxCount = config?.settings?.maxAttachments ?? 5;
|
|
475
|
+
if (files.length > maxCount) {
|
|
476
|
+
errors2.push({
|
|
477
|
+
field: "attachments",
|
|
478
|
+
file: "",
|
|
479
|
+
error: "count",
|
|
480
|
+
message: `Maximum ${maxCount} files allowed`
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
files.forEach((file) => {
|
|
484
|
+
if (file.size > maxSize) {
|
|
485
|
+
errors2.push({
|
|
486
|
+
field: "attachments",
|
|
487
|
+
file: file.name,
|
|
488
|
+
error: "size",
|
|
489
|
+
message: `File "${file.name}" exceeds maximum size of ${formatBytes(maxSize)}`
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
setFileErrors(errors2);
|
|
494
|
+
return errors2;
|
|
495
|
+
}, [config?.settings?.maxAttachmentSize, config?.settings?.maxAttachments]);
|
|
496
|
+
const allowsAttachments = config?.settings?.allowAttachments ?? false;
|
|
497
|
+
const maxAttachments = config?.settings?.maxAttachments ?? 5;
|
|
498
|
+
const maxAttachmentSize = config?.settings?.maxAttachmentSize ?? 10 * 1024 * 1024;
|
|
499
|
+
return {
|
|
500
|
+
config,
|
|
501
|
+
schema: config?.schema ?? null,
|
|
502
|
+
isLoading,
|
|
503
|
+
isInitializing,
|
|
504
|
+
isSubmitted,
|
|
505
|
+
isSuccess: isSubmitted,
|
|
506
|
+
errors,
|
|
507
|
+
fileErrors,
|
|
508
|
+
error,
|
|
509
|
+
values,
|
|
510
|
+
uploadProgress,
|
|
511
|
+
initialize,
|
|
512
|
+
setValue,
|
|
513
|
+
setValues: setValuesHandler,
|
|
514
|
+
validate,
|
|
515
|
+
validateFiles,
|
|
516
|
+
submit,
|
|
517
|
+
reset,
|
|
518
|
+
clearErrors,
|
|
519
|
+
requiresCaptcha: config?.settings?.captcha?.enabled ?? false,
|
|
520
|
+
captchaProvider: config?.settings?.captcha?.provider,
|
|
521
|
+
captchaSiteKey: config?.settings?.captcha?.siteKey,
|
|
522
|
+
honeypotEnabled: config?.settings?.honeypot ?? false,
|
|
523
|
+
allowsAttachments,
|
|
524
|
+
maxAttachments,
|
|
525
|
+
maxAttachmentSize
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// react/forms-expert-form.tsx
|
|
530
|
+
import { useState as useState2, useEffect as useEffect2, useRef } from "react";
|
|
531
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
532
|
+
var defaultStyling = {
|
|
533
|
+
theme: "light",
|
|
534
|
+
primaryColor: "#3b82f6",
|
|
535
|
+
backgroundColor: "#ffffff",
|
|
536
|
+
textColor: "#1f2937",
|
|
537
|
+
borderRadius: "md",
|
|
538
|
+
fontSize: "md",
|
|
539
|
+
buttonStyle: "filled",
|
|
540
|
+
labelPosition: "top"
|
|
541
|
+
};
|
|
542
|
+
function getBorderRadius(radius) {
|
|
543
|
+
switch (radius) {
|
|
544
|
+
case "none":
|
|
545
|
+
return "0";
|
|
546
|
+
case "sm":
|
|
547
|
+
return "0.125rem";
|
|
548
|
+
case "md":
|
|
549
|
+
return "0.375rem";
|
|
550
|
+
case "lg":
|
|
551
|
+
return "0.5rem";
|
|
552
|
+
default:
|
|
553
|
+
return "0.375rem";
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function getFontSize(size) {
|
|
557
|
+
switch (size) {
|
|
558
|
+
case "sm":
|
|
559
|
+
return "0.875rem";
|
|
560
|
+
case "md":
|
|
561
|
+
return "1rem";
|
|
562
|
+
case "lg":
|
|
563
|
+
return "1.125rem";
|
|
564
|
+
default:
|
|
565
|
+
return "1rem";
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function FormsExpertForm({
|
|
569
|
+
config,
|
|
570
|
+
slug,
|
|
571
|
+
trackViews,
|
|
572
|
+
submitText = "Submit",
|
|
573
|
+
onSuccess,
|
|
574
|
+
onError,
|
|
575
|
+
onValidationError,
|
|
576
|
+
className,
|
|
577
|
+
style
|
|
578
|
+
}) {
|
|
579
|
+
const form = useForm({
|
|
580
|
+
slug,
|
|
581
|
+
config,
|
|
582
|
+
trackViews,
|
|
583
|
+
onSuccess,
|
|
584
|
+
onError,
|
|
585
|
+
onValidationError,
|
|
586
|
+
autoInit: true
|
|
587
|
+
});
|
|
588
|
+
const [captchaToken, setCaptchaToken] = useState2(null);
|
|
589
|
+
const captchaContainerRef = useRef(null);
|
|
590
|
+
const captchaWidgetId = useRef(null);
|
|
591
|
+
const captchaScriptLoaded = useRef(false);
|
|
592
|
+
useEffect2(() => {
|
|
593
|
+
if (!form.requiresCaptcha || !form.captchaSiteKey || !form.captchaProvider) return;
|
|
594
|
+
if (captchaScriptLoaded.current) return;
|
|
595
|
+
const provider = form.captchaProvider;
|
|
596
|
+
const siteKey = form.captchaSiteKey;
|
|
597
|
+
const renderWidget = () => {
|
|
598
|
+
if (!captchaContainerRef.current) return;
|
|
599
|
+
const w2 = window;
|
|
600
|
+
if (provider === "turnstile" && w2.turnstile) {
|
|
601
|
+
captchaWidgetId.current = w2.turnstile.render(captchaContainerRef.current, {
|
|
602
|
+
sitekey: siteKey,
|
|
603
|
+
callback: (token) => setCaptchaToken(token),
|
|
604
|
+
"expired-callback": () => setCaptchaToken(null),
|
|
605
|
+
"error-callback": () => setCaptchaToken(null)
|
|
606
|
+
});
|
|
607
|
+
} else if (provider === "hcaptcha" && w2.hcaptcha) {
|
|
608
|
+
captchaWidgetId.current = w2.hcaptcha.render(captchaContainerRef.current, {
|
|
609
|
+
sitekey: siteKey,
|
|
610
|
+
callback: (token) => setCaptchaToken(token),
|
|
611
|
+
"expired-callback": () => setCaptchaToken(null),
|
|
612
|
+
"error-callback": () => setCaptchaToken(null)
|
|
613
|
+
});
|
|
614
|
+
} else if (provider === "recaptcha" && w2.grecaptcha) {
|
|
615
|
+
w2.grecaptcha.ready(() => {
|
|
616
|
+
w2.grecaptcha.execute(siteKey, { action: "submit" }).then((token) => {
|
|
617
|
+
setCaptchaToken(token);
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
const w = window;
|
|
623
|
+
if (provider === "turnstile" && w.turnstile || provider === "hcaptcha" && w.hcaptcha || provider === "recaptcha" && w.grecaptcha) {
|
|
624
|
+
captchaScriptLoaded.current = true;
|
|
625
|
+
renderWidget();
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const script = document.createElement("script");
|
|
629
|
+
if (provider === "turnstile") {
|
|
630
|
+
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
631
|
+
} else if (provider === "hcaptcha") {
|
|
632
|
+
script.src = "https://js.hcaptcha.com/1/api.js?render=explicit";
|
|
633
|
+
} else if (provider === "recaptcha") {
|
|
634
|
+
script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
|
|
635
|
+
}
|
|
636
|
+
script.async = true;
|
|
637
|
+
script.defer = true;
|
|
638
|
+
script.onload = () => {
|
|
639
|
+
captchaScriptLoaded.current = true;
|
|
640
|
+
setTimeout(renderWidget, 100);
|
|
641
|
+
};
|
|
642
|
+
document.head.appendChild(script);
|
|
643
|
+
return () => {
|
|
644
|
+
if (captchaWidgetId.current !== null) {
|
|
645
|
+
const w2 = window;
|
|
646
|
+
if (provider === "turnstile" && w2.turnstile) {
|
|
647
|
+
w2.turnstile.remove(captchaWidgetId.current);
|
|
648
|
+
} else if (provider === "hcaptcha" && w2.hcaptcha) {
|
|
649
|
+
w2.hcaptcha.reset(captchaWidgetId.current);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
}, [form.requiresCaptcha, form.captchaProvider, form.captchaSiteKey]);
|
|
654
|
+
const resetCaptcha = () => {
|
|
655
|
+
setCaptchaToken(null);
|
|
656
|
+
if (captchaWidgetId.current !== null) {
|
|
657
|
+
const w = window;
|
|
658
|
+
if (form.captchaProvider === "turnstile" && w.turnstile) {
|
|
659
|
+
w.turnstile.reset(captchaWidgetId.current);
|
|
660
|
+
} else if (form.captchaProvider === "hcaptcha" && w.hcaptcha) {
|
|
661
|
+
w.hcaptcha.reset(captchaWidgetId.current);
|
|
662
|
+
} else if (form.captchaProvider === "recaptcha" && w.grecaptcha) {
|
|
663
|
+
w.grecaptcha.execute(form.captchaSiteKey, { action: "submit" }).then((token) => {
|
|
664
|
+
setCaptchaToken(token);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
const styling = form.config?.schema?.styling || defaultStyling;
|
|
670
|
+
const radius = getBorderRadius(styling.borderRadius);
|
|
671
|
+
const fontSize = getFontSize(styling.fontSize);
|
|
672
|
+
const handleSubmit = async (e) => {
|
|
673
|
+
e.preventDefault();
|
|
674
|
+
const result = await form.submit(captchaToken || void 0);
|
|
675
|
+
if (!result) {
|
|
676
|
+
resetCaptcha();
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
const handleChange = (e) => {
|
|
680
|
+
const { name, value, type } = e.target;
|
|
681
|
+
if (type === "checkbox") {
|
|
682
|
+
form.setValue(name, e.target.checked);
|
|
683
|
+
} else if (type === "file") {
|
|
684
|
+
form.setValue(name, e.target.files?.[0]);
|
|
685
|
+
} else {
|
|
686
|
+
form.setValue(name, value);
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
if (form.isInitializing) {
|
|
690
|
+
return /* @__PURE__ */ jsx2(
|
|
691
|
+
"div",
|
|
692
|
+
{
|
|
693
|
+
className,
|
|
694
|
+
style: {
|
|
695
|
+
padding: "2rem",
|
|
696
|
+
textAlign: "center",
|
|
697
|
+
color: styling.textColor,
|
|
698
|
+
...style
|
|
699
|
+
},
|
|
700
|
+
children: "Loading form..."
|
|
701
|
+
}
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
if (!form.config?.active) {
|
|
705
|
+
return /* @__PURE__ */ jsx2(
|
|
706
|
+
"div",
|
|
707
|
+
{
|
|
708
|
+
className,
|
|
709
|
+
style: {
|
|
710
|
+
padding: "2rem",
|
|
711
|
+
textAlign: "center",
|
|
712
|
+
color: "#ef4444",
|
|
713
|
+
...style
|
|
714
|
+
},
|
|
715
|
+
children: "This form is not available"
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
if (form.isSubmitted) {
|
|
720
|
+
return /* @__PURE__ */ jsxs(
|
|
721
|
+
"div",
|
|
722
|
+
{
|
|
723
|
+
className,
|
|
724
|
+
style: {
|
|
725
|
+
padding: "2rem",
|
|
726
|
+
textAlign: "center",
|
|
727
|
+
backgroundColor: styling.backgroundColor,
|
|
728
|
+
color: styling.textColor,
|
|
729
|
+
borderRadius: radius,
|
|
730
|
+
...style
|
|
731
|
+
},
|
|
732
|
+
children: [
|
|
733
|
+
/* @__PURE__ */ jsx2(
|
|
734
|
+
"svg",
|
|
735
|
+
{
|
|
736
|
+
style: {
|
|
737
|
+
width: "3rem",
|
|
738
|
+
height: "3rem",
|
|
739
|
+
margin: "0 auto 1rem",
|
|
740
|
+
color: "#22c55e"
|
|
741
|
+
},
|
|
742
|
+
fill: "none",
|
|
743
|
+
stroke: "currentColor",
|
|
744
|
+
viewBox: "0 0 24 24",
|
|
745
|
+
children: /* @__PURE__ */ jsx2(
|
|
746
|
+
"path",
|
|
747
|
+
{
|
|
748
|
+
strokeLinecap: "round",
|
|
749
|
+
strokeLinejoin: "round",
|
|
750
|
+
strokeWidth: 2,
|
|
751
|
+
d: "M5 13l4 4L19 7"
|
|
752
|
+
}
|
|
753
|
+
)
|
|
754
|
+
}
|
|
755
|
+
),
|
|
756
|
+
/* @__PURE__ */ jsx2("p", { style: { fontSize: "1.125rem", fontWeight: 500 }, children: form.config.settings?.successMessage || "Form submitted successfully!" }),
|
|
757
|
+
/* @__PURE__ */ jsx2(
|
|
758
|
+
"button",
|
|
759
|
+
{
|
|
760
|
+
type: "button",
|
|
761
|
+
onClick: form.reset,
|
|
762
|
+
style: {
|
|
763
|
+
marginTop: "1rem",
|
|
764
|
+
padding: "0.5rem 1rem",
|
|
765
|
+
backgroundColor: "transparent",
|
|
766
|
+
color: styling.primaryColor,
|
|
767
|
+
border: `1px solid ${styling.primaryColor}`,
|
|
768
|
+
borderRadius: radius,
|
|
769
|
+
cursor: "pointer"
|
|
770
|
+
},
|
|
771
|
+
children: "Submit another response"
|
|
772
|
+
}
|
|
773
|
+
)
|
|
774
|
+
]
|
|
775
|
+
}
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
const fields = form.config.schema?.fields || [];
|
|
779
|
+
const showBranding = form.config.branding?.enabled !== false;
|
|
780
|
+
const brandingText = form.config.branding?.text || "Powered by Mira";
|
|
781
|
+
const brandingUrl = form.config.branding?.url || "https://mira.io";
|
|
782
|
+
return /* @__PURE__ */ jsxs(
|
|
783
|
+
"form",
|
|
784
|
+
{
|
|
785
|
+
onSubmit: handleSubmit,
|
|
786
|
+
className,
|
|
787
|
+
style: {
|
|
788
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
789
|
+
fontSize,
|
|
790
|
+
backgroundColor: styling.backgroundColor,
|
|
791
|
+
color: styling.textColor,
|
|
792
|
+
padding: "1.5rem",
|
|
793
|
+
borderRadius: radius,
|
|
794
|
+
...style
|
|
795
|
+
},
|
|
796
|
+
children: [
|
|
797
|
+
fields.map((field) => /* @__PURE__ */ jsx2(
|
|
798
|
+
FormFieldInput,
|
|
799
|
+
{
|
|
800
|
+
field,
|
|
801
|
+
value: form.values[field.name],
|
|
802
|
+
error: form.errors[field.name],
|
|
803
|
+
onChange: handleChange,
|
|
804
|
+
styling
|
|
805
|
+
},
|
|
806
|
+
field.name
|
|
807
|
+
)),
|
|
808
|
+
form.honeypotEnabled && /* @__PURE__ */ jsx2("div", { style: { position: "absolute", left: "-9999px", opacity: 0, height: 0, overflow: "hidden" }, "aria-hidden": "true", children: /* @__PURE__ */ jsx2("input", { type: "text", name: "_hp", tabIndex: -1, autoComplete: "off" }) }),
|
|
809
|
+
form.requiresCaptcha && form.captchaProvider !== "recaptcha" && /* @__PURE__ */ jsx2("div", { ref: captchaContainerRef, style: { marginTop: "1rem" } }),
|
|
810
|
+
/* @__PURE__ */ jsx2(
|
|
811
|
+
"button",
|
|
812
|
+
{
|
|
813
|
+
type: "submit",
|
|
814
|
+
disabled: form.isLoading,
|
|
815
|
+
style: {
|
|
816
|
+
width: "100%",
|
|
817
|
+
padding: "0.625rem 1.25rem",
|
|
818
|
+
marginTop: "1rem",
|
|
819
|
+
fontWeight: 500,
|
|
820
|
+
fontSize,
|
|
821
|
+
fontFamily: "inherit",
|
|
822
|
+
borderRadius: radius,
|
|
823
|
+
cursor: form.isLoading ? "not-allowed" : "pointer",
|
|
824
|
+
opacity: form.isLoading ? 0.5 : 1,
|
|
825
|
+
backgroundColor: styling.buttonStyle === "filled" ? styling.primaryColor : "transparent",
|
|
826
|
+
color: styling.buttonStyle === "filled" ? "white" : styling.primaryColor,
|
|
827
|
+
border: styling.buttonStyle === "filled" ? "none" : `2px solid ${styling.primaryColor}`
|
|
828
|
+
},
|
|
829
|
+
children: form.isLoading ? "Submitting..." : submitText
|
|
830
|
+
}
|
|
831
|
+
),
|
|
832
|
+
showBranding && /* @__PURE__ */ jsx2(
|
|
833
|
+
"div",
|
|
834
|
+
{
|
|
835
|
+
style: {
|
|
836
|
+
textAlign: "center",
|
|
837
|
+
marginTop: "1rem",
|
|
838
|
+
paddingTop: "0.75rem",
|
|
839
|
+
borderTop: `1px solid ${styling.theme === "dark" ? "#374151" : "#e5e7eb"}`
|
|
840
|
+
},
|
|
841
|
+
children: /* @__PURE__ */ jsx2(
|
|
842
|
+
"a",
|
|
843
|
+
{
|
|
844
|
+
href: brandingUrl,
|
|
845
|
+
target: "_blank",
|
|
846
|
+
rel: "noopener noreferrer",
|
|
847
|
+
style: {
|
|
848
|
+
color: styling.theme === "dark" ? "#9ca3af" : "#6b7280",
|
|
849
|
+
textDecoration: "none",
|
|
850
|
+
fontSize: "0.75rem"
|
|
851
|
+
},
|
|
852
|
+
children: brandingText
|
|
853
|
+
}
|
|
854
|
+
)
|
|
855
|
+
}
|
|
856
|
+
)
|
|
857
|
+
]
|
|
858
|
+
}
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
function FormFieldInput({
|
|
862
|
+
field,
|
|
863
|
+
value,
|
|
864
|
+
error,
|
|
865
|
+
onChange,
|
|
866
|
+
styling
|
|
867
|
+
}) {
|
|
868
|
+
const radius = getBorderRadius(styling.borderRadius);
|
|
869
|
+
const fontSize = getFontSize(styling.fontSize);
|
|
870
|
+
const inputStyle = {
|
|
871
|
+
width: "100%",
|
|
872
|
+
padding: "0.5rem 0.75rem",
|
|
873
|
+
border: `1px solid ${error ? "#ef4444" : styling.theme === "dark" ? "#4b5563" : "#d1d5db"}`,
|
|
874
|
+
borderRadius: radius,
|
|
875
|
+
fontSize,
|
|
876
|
+
fontFamily: "inherit",
|
|
877
|
+
backgroundColor: styling.theme === "dark" ? "#374151" : "#ffffff",
|
|
878
|
+
color: styling.textColor
|
|
879
|
+
};
|
|
880
|
+
if (field.type === "checkbox") {
|
|
881
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", marginBottom: "1rem" }, children: [
|
|
882
|
+
/* @__PURE__ */ jsx2(
|
|
883
|
+
"input",
|
|
884
|
+
{
|
|
885
|
+
type: "checkbox",
|
|
886
|
+
id: field.name,
|
|
887
|
+
name: field.name,
|
|
888
|
+
checked: Boolean(value),
|
|
889
|
+
onChange,
|
|
890
|
+
required: field.required,
|
|
891
|
+
style: { width: "1rem", height: "1rem", accentColor: styling.primaryColor }
|
|
892
|
+
}
|
|
893
|
+
),
|
|
894
|
+
/* @__PURE__ */ jsxs("label", { htmlFor: field.name, children: [
|
|
895
|
+
field.label || field.name,
|
|
896
|
+
field.required && /* @__PURE__ */ jsx2("span", { style: { color: "#ef4444", marginLeft: "0.25rem" }, children: "*" })
|
|
897
|
+
] }),
|
|
898
|
+
error && /* @__PURE__ */ jsx2("span", { style: { color: "#ef4444", fontSize: "0.875rem", marginLeft: "0.5rem" }, children: error })
|
|
899
|
+
] });
|
|
900
|
+
}
|
|
901
|
+
return /* @__PURE__ */ jsxs(
|
|
902
|
+
"div",
|
|
903
|
+
{
|
|
904
|
+
style: {
|
|
905
|
+
marginBottom: "1rem",
|
|
906
|
+
...styling.labelPosition === "left" ? { display: "flex", alignItems: "flex-start", gap: "1rem" } : {}
|
|
907
|
+
},
|
|
908
|
+
children: [
|
|
909
|
+
/* @__PURE__ */ jsxs(
|
|
910
|
+
"label",
|
|
911
|
+
{
|
|
912
|
+
htmlFor: field.name,
|
|
913
|
+
style: {
|
|
914
|
+
display: "block",
|
|
915
|
+
fontWeight: 500,
|
|
916
|
+
...styling.labelPosition === "left" ? { width: "33%", flexShrink: 0, paddingTop: "0.5rem" } : { marginBottom: "0.25rem" }
|
|
917
|
+
},
|
|
918
|
+
children: [
|
|
919
|
+
field.label || field.name,
|
|
920
|
+
field.required && /* @__PURE__ */ jsx2("span", { style: { color: "#ef4444", marginLeft: "0.25rem" }, children: "*" })
|
|
921
|
+
]
|
|
922
|
+
}
|
|
923
|
+
),
|
|
924
|
+
/* @__PURE__ */ jsxs("div", { style: styling.labelPosition === "left" ? { flex: 1 } : {}, children: [
|
|
925
|
+
field.type === "textarea" ? /* @__PURE__ */ jsx2(
|
|
926
|
+
"textarea",
|
|
927
|
+
{
|
|
928
|
+
id: field.name,
|
|
929
|
+
name: field.name,
|
|
930
|
+
value: String(value || ""),
|
|
931
|
+
onChange,
|
|
932
|
+
placeholder: field.placeholder,
|
|
933
|
+
required: field.required,
|
|
934
|
+
style: { ...inputStyle, minHeight: "100px", resize: "vertical" }
|
|
935
|
+
}
|
|
936
|
+
) : field.type === "select" ? /* @__PURE__ */ jsxs(
|
|
937
|
+
"select",
|
|
938
|
+
{
|
|
939
|
+
id: field.name,
|
|
940
|
+
name: field.name,
|
|
941
|
+
value: String(value || ""),
|
|
942
|
+
onChange,
|
|
943
|
+
required: field.required,
|
|
944
|
+
style: inputStyle,
|
|
945
|
+
children: [
|
|
946
|
+
/* @__PURE__ */ jsx2("option", { value: "", children: "Select an option..." }),
|
|
947
|
+
field.options?.map((opt) => {
|
|
948
|
+
const val = typeof opt === "string" ? opt : opt.value;
|
|
949
|
+
const label = typeof opt === "string" ? opt : opt.label;
|
|
950
|
+
return /* @__PURE__ */ jsx2("option", { value: val, children: label }, val);
|
|
951
|
+
})
|
|
952
|
+
]
|
|
953
|
+
}
|
|
954
|
+
) : field.type === "file" ? /* @__PURE__ */ jsx2(
|
|
955
|
+
"input",
|
|
956
|
+
{
|
|
957
|
+
type: "file",
|
|
958
|
+
id: field.name,
|
|
959
|
+
name: field.name,
|
|
960
|
+
onChange,
|
|
961
|
+
required: field.required,
|
|
962
|
+
accept: field.allowedMimeTypes?.join(","),
|
|
963
|
+
multiple: field.multiple,
|
|
964
|
+
style: inputStyle
|
|
965
|
+
}
|
|
966
|
+
) : /* @__PURE__ */ jsx2(
|
|
967
|
+
"input",
|
|
968
|
+
{
|
|
969
|
+
type: field.type,
|
|
970
|
+
id: field.name,
|
|
971
|
+
name: field.name,
|
|
972
|
+
value: String(value || ""),
|
|
973
|
+
onChange,
|
|
974
|
+
placeholder: field.placeholder,
|
|
975
|
+
required: field.required,
|
|
976
|
+
style: inputStyle
|
|
977
|
+
}
|
|
978
|
+
),
|
|
979
|
+
error && /* @__PURE__ */ jsx2("div", { style: { color: "#ef4444", fontSize: "0.875rem", marginTop: "0.25rem" }, children: error })
|
|
980
|
+
] })
|
|
981
|
+
]
|
|
982
|
+
}
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
export {
|
|
986
|
+
FormsExpertForm,
|
|
987
|
+
FormsProvider,
|
|
988
|
+
useForm,
|
|
989
|
+
useFormsSDK
|
|
990
|
+
};
|
|
991
|
+
//# sourceMappingURL=index.js.map
|