@usefillo/react 0.5.4 → 0.6.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 +68 -14
- package/dist/index.d.ts +106 -6
- package/dist/index.js +475 -149
- package/dist/styles.css +15 -11
- package/dist/styles.unlayered.css +700 -0
- package/package.json +10 -6
package/dist/index.js
CHANGED
|
@@ -1,23 +1,66 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/FilloForm.tsx
|
|
4
|
-
import { useEffect as useEffect7, useId as useId2, useMemo as
|
|
4
|
+
import { useEffect as useEffect7, useId as useId2, useMemo as useMemo5, useRef as useRef5, useState as useState6 } from "react";
|
|
5
5
|
import {
|
|
6
6
|
FilloError as FilloError2,
|
|
7
7
|
createClient,
|
|
8
|
-
|
|
8
|
+
needsExplicitSubmit,
|
|
9
9
|
normalizeFormSchema,
|
|
10
10
|
normalizeFormTheme,
|
|
11
11
|
visibleFields as visibleFields2
|
|
12
12
|
} from "@usefillo/core";
|
|
13
|
+
import { resolveStrings, slotClass as slotClass3 } from "@usefillo/core";
|
|
14
|
+
|
|
15
|
+
// src/appearance.tsx
|
|
16
|
+
import { createContext, useContext } from "react";
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_STRINGS,
|
|
19
|
+
slotClass
|
|
20
|
+
} from "@usefillo/core";
|
|
21
|
+
var FilloAppearanceContext = createContext(void 0);
|
|
22
|
+
function useSlotClass(base, state) {
|
|
23
|
+
const appearance = useContext(FilloAppearanceContext);
|
|
24
|
+
return slotClass(base, appearance, state);
|
|
25
|
+
}
|
|
26
|
+
function useFilloAppearance() {
|
|
27
|
+
return useContext(FilloAppearanceContext);
|
|
28
|
+
}
|
|
29
|
+
function useFieldSlots(field, error) {
|
|
30
|
+
const appearance = useContext(FilloAppearanceContext);
|
|
31
|
+
const state = {
|
|
32
|
+
kind: field.kind,
|
|
33
|
+
fieldId: field.id,
|
|
34
|
+
invalid: Boolean(error),
|
|
35
|
+
required: Boolean(field.required)
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
wrapperProps: (base) => ({
|
|
39
|
+
className: slotClass(base, appearance, { slot: "field", ...state }),
|
|
40
|
+
"data-fillo": "field",
|
|
41
|
+
"data-field": field.id,
|
|
42
|
+
"data-kind": field.kind,
|
|
43
|
+
"data-invalid": error ? "" : void 0,
|
|
44
|
+
"data-required": field.required ? "" : void 0
|
|
45
|
+
}),
|
|
46
|
+
label: slotClass("fillo-label", appearance, { slot: "label", ...state }),
|
|
47
|
+
description: slotClass("fillo-description", appearance, { slot: "fieldDescription", ...state }),
|
|
48
|
+
error: slotClass("fillo-error", appearance, { slot: "error", ...state }),
|
|
49
|
+
control: (base) => slotClass(base, appearance, { slot: "control", ...state })
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
var FilloStringsContext = createContext(DEFAULT_STRINGS);
|
|
53
|
+
function useStrings() {
|
|
54
|
+
return useContext(FilloStringsContext);
|
|
55
|
+
}
|
|
13
56
|
|
|
14
57
|
// src/context.ts
|
|
15
58
|
import { allFields } from "@usefillo/core";
|
|
16
|
-
import { createContext, useContext, useId, useMemo } from "react";
|
|
17
|
-
var FilloContext =
|
|
18
|
-
var FilloInstanceIdContext =
|
|
59
|
+
import { createContext as createContext2, useContext as useContext2, useId, useMemo } from "react";
|
|
60
|
+
var FilloContext = createContext2(null);
|
|
61
|
+
var FilloInstanceIdContext = createContext2(null);
|
|
19
62
|
function useFilloInstanceId() {
|
|
20
|
-
const instanceId =
|
|
63
|
+
const instanceId = useContext2(FilloInstanceIdContext);
|
|
21
64
|
const fallbackId = useId().replace(/:/g, "");
|
|
22
65
|
return instanceId ?? fallbackId;
|
|
23
66
|
}
|
|
@@ -32,7 +75,7 @@ function createFilloFieldIds(instanceId, fieldId) {
|
|
|
32
75
|
};
|
|
33
76
|
}
|
|
34
77
|
function useFillo() {
|
|
35
|
-
const api =
|
|
78
|
+
const api = useContext2(FilloContext);
|
|
36
79
|
if (!api) throw new Error("useFillo must be used inside <FilloForm> or <FilloProvider>");
|
|
37
80
|
return api;
|
|
38
81
|
}
|
|
@@ -75,7 +118,9 @@ function useFilloController(options) {
|
|
|
75
118
|
onSubmitted: (id, d) => optsRef.current.onSubmitted?.(id, d),
|
|
76
119
|
getHoneypot: () => optsRef.current.getHoneypot?.() ?? "",
|
|
77
120
|
skipValidation: options.skipValidation,
|
|
78
|
-
surface: options.surface ?? "
|
|
121
|
+
surface: options.surface ?? "headless",
|
|
122
|
+
// Wrapped so the engine (created once) always calls the latest resolver.
|
|
123
|
+
resolveFormId: options.resolveFormId ? () => optsRef.current.resolveFormId() : void 0
|
|
79
124
|
});
|
|
80
125
|
}
|
|
81
126
|
const store = storeRef.current;
|
|
@@ -109,16 +154,33 @@ import {
|
|
|
109
154
|
} from "@usefillo/core";
|
|
110
155
|
import { useEffect as useEffect2, useState } from "react";
|
|
111
156
|
import { defineForm, isCodeForm as isCodeForm2, syncCodeForm as syncCodeForm2 } from "@usefillo/core";
|
|
157
|
+
var noticedSync = /* @__PURE__ */ new Set();
|
|
158
|
+
function noticeSync(handle, kind, message) {
|
|
159
|
+
const key = `${handle}:${kind}`;
|
|
160
|
+
if (noticedSync.has(key)) return;
|
|
161
|
+
noticedSync.add(key);
|
|
162
|
+
console.info(message);
|
|
163
|
+
}
|
|
112
164
|
function useCodeFormSync(form, client, onError) {
|
|
113
165
|
const codeForm = isCodeForm(form) ? form : null;
|
|
114
|
-
const [
|
|
166
|
+
const [synced, setSynced] = useState({ formId: null, status: null });
|
|
115
167
|
const hasKey = Boolean(client?.key);
|
|
116
168
|
const handle = codeForm?.id;
|
|
117
169
|
const contentKey = codeForm ? contentHash(JSON.stringify(codeForm.schema) + JSON.stringify(codeForm.theme ?? null)) : null;
|
|
118
170
|
useEffect2(() => {
|
|
119
171
|
if (!codeForm || !client?.key) return;
|
|
120
172
|
let cancelled = false;
|
|
121
|
-
syncCodeForm(client, codeForm).then((r) =>
|
|
173
|
+
syncCodeForm(client, codeForm).then((r) => {
|
|
174
|
+
if (cancelled) return;
|
|
175
|
+
setSynced({ formId: r.formId, status: r.status ?? null });
|
|
176
|
+
const dashboardUrl = `${client.baseUrl || "https://fillo.so"}/forms/${r.formId}`;
|
|
177
|
+
if (r.warning) console.warn(`[fillo] ${r.warning}`);
|
|
178
|
+
if (r.staged) {
|
|
179
|
+
noticeSync(codeForm.id, "staged", `[fillo] "${codeForm.id}": changes staged as a draft \u2014 publish them at ${dashboardUrl}`);
|
|
180
|
+
} else if (r.status === "draft") {
|
|
181
|
+
noticeSync(codeForm.id, "draft", `[fillo] "${codeForm.id}" is a draft \u2014 it renders here but won't accept responses until published: ${dashboardUrl}`);
|
|
182
|
+
}
|
|
183
|
+
}).catch((err) => {
|
|
122
184
|
const error = err instanceof FilloError ? err : new FilloError(String(err), 0);
|
|
123
185
|
console.warn("[fillo] form sync failed:", error.message);
|
|
124
186
|
if (!cancelled) onError?.(error);
|
|
@@ -127,12 +189,12 @@ function useCodeFormSync(form, client, onError) {
|
|
|
127
189
|
cancelled = true;
|
|
128
190
|
};
|
|
129
191
|
}, [hasKey, handle, contentKey, client, onError]);
|
|
130
|
-
return
|
|
192
|
+
return synced;
|
|
131
193
|
}
|
|
132
194
|
|
|
133
195
|
// src/fields.tsx
|
|
134
|
-
import { isField, pipeBlock,
|
|
135
|
-
import { memo, useEffect as useEffect6, useState as useState5 } from "react";
|
|
196
|
+
import { isField, pipeBlock, shouldAutoSubmit, slotClass as slotClass2, visibleFields } from "@usefillo/core";
|
|
197
|
+
import { memo, useEffect as useEffect6, useMemo as useMemo4, useState as useState5 } from "react";
|
|
136
198
|
|
|
137
199
|
// src/phone.tsx
|
|
138
200
|
import {
|
|
@@ -193,6 +255,8 @@ function positionPhonePopover(anchor, popover) {
|
|
|
193
255
|
}
|
|
194
256
|
function PhoneField({ field, value, error, setValue, ids: providedIds }) {
|
|
195
257
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
258
|
+
const slots = useFieldSlots(field, error);
|
|
259
|
+
const strings = useStrings();
|
|
196
260
|
const schema = field;
|
|
197
261
|
const stored = typeof value === "string" ? value : "";
|
|
198
262
|
const [country, setCountry] = useState2(
|
|
@@ -247,12 +311,12 @@ function PhoneField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
247
311
|
inputRef.current?.focus();
|
|
248
312
|
}
|
|
249
313
|
const describedBy = [field.description ? ids.descriptionId : null, error ? ids.errorId : null].filter(Boolean).join(" ") || void 0;
|
|
250
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
251
|
-
/* @__PURE__ */ jsxs("label", { className: "fillo
|
|
314
|
+
return /* @__PURE__ */ jsxs("div", { ...slots.wrapperProps(`fillo-field fillo-field--phone${error ? " fillo-field--error" : ""}`), children: [
|
|
315
|
+
/* @__PURE__ */ jsxs("label", { className: slots.label, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
|
|
252
316
|
field.label,
|
|
253
|
-
!field.required && /* @__PURE__ */ jsx("span", { className: "fillo-optional", children:
|
|
317
|
+
!field.required && /* @__PURE__ */ jsx("span", { className: "fillo-optional", children: strings.optional })
|
|
254
318
|
] }),
|
|
255
|
-
field.description && /* @__PURE__ */ jsx("p", { className: "fillo
|
|
319
|
+
field.description && /* @__PURE__ */ jsx("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
256
320
|
/* @__PURE__ */ jsxs("div", { className: "fillo-phone", children: [
|
|
257
321
|
/* @__PURE__ */ jsx(CountrySelect, { country, onPick: pickCountry, labelledBy: ids.labelId }),
|
|
258
322
|
/* @__PURE__ */ jsx(
|
|
@@ -260,7 +324,8 @@ function PhoneField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
260
324
|
{
|
|
261
325
|
ref: inputRef,
|
|
262
326
|
id: ids.inputId,
|
|
263
|
-
className: "fillo-input fillo-phone-input",
|
|
327
|
+
className: slots.control("fillo-input fillo-phone-input"),
|
|
328
|
+
"data-fillo": "control",
|
|
264
329
|
type: "tel",
|
|
265
330
|
inputMode: "tel",
|
|
266
331
|
autoComplete: "tel-national",
|
|
@@ -273,7 +338,7 @@ function PhoneField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
273
338
|
}
|
|
274
339
|
)
|
|
275
340
|
] }),
|
|
276
|
-
error && /* @__PURE__ */ jsx("p", { className: "fillo
|
|
341
|
+
error && /* @__PURE__ */ jsx("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
277
342
|
] });
|
|
278
343
|
}
|
|
279
344
|
function CountrySelect({
|
|
@@ -426,6 +491,8 @@ import { useEffect as useEffect4, useRef as useRef3, useState as useState3 } fro
|
|
|
426
491
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
427
492
|
function SignatureField({ field, value, error, setValue, ids: providedIds }) {
|
|
428
493
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
494
|
+
const slots = useFieldSlots(field, error);
|
|
495
|
+
const strings = useStrings();
|
|
429
496
|
const typedInputId = `${ids.inputId}-typed`;
|
|
430
497
|
const canvasRef = useRef3(null);
|
|
431
498
|
const drawing = useRef3(false);
|
|
@@ -507,12 +574,12 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
507
574
|
setValue(null);
|
|
508
575
|
}
|
|
509
576
|
const describedBy = [field.description ? ids.descriptionId : null, error ? ids.errorId : null].filter(Boolean).join(" ") || void 0;
|
|
510
|
-
return /* @__PURE__ */ jsxs2("div", {
|
|
511
|
-
/* @__PURE__ */ jsxs2("span", { className: "fillo
|
|
577
|
+
return /* @__PURE__ */ jsxs2("div", { ...slots.wrapperProps(`fillo-field fillo-field--signature${error ? " fillo-field--error" : ""}`), children: [
|
|
578
|
+
/* @__PURE__ */ jsxs2("span", { className: slots.label, "data-fillo": "label", id: ids.labelId, children: [
|
|
512
579
|
field.label,
|
|
513
|
-
!field.required && /* @__PURE__ */ jsx2("span", { className: "fillo-optional", children:
|
|
580
|
+
!field.required && /* @__PURE__ */ jsx2("span", { className: "fillo-optional", children: strings.optional })
|
|
514
581
|
] }),
|
|
515
|
-
field.description && /* @__PURE__ */ jsx2("p", { className: "fillo
|
|
582
|
+
field.description && /* @__PURE__ */ jsx2("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
516
583
|
/* @__PURE__ */ jsxs2(
|
|
517
584
|
"div",
|
|
518
585
|
{
|
|
@@ -520,7 +587,7 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
520
587
|
"aria-labelledby": ids.labelId,
|
|
521
588
|
"aria-describedby": describedBy,
|
|
522
589
|
children: [
|
|
523
|
-
/* @__PURE__ */ jsxs2("div", { className: "fillo-signature", children: [
|
|
590
|
+
/* @__PURE__ */ jsxs2("div", { className: slots.control("fillo-signature"), "data-fillo": "control", children: [
|
|
524
591
|
/* @__PURE__ */ jsx2(
|
|
525
592
|
"canvas",
|
|
526
593
|
{
|
|
@@ -560,7 +627,7 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
560
627
|
]
|
|
561
628
|
}
|
|
562
629
|
),
|
|
563
|
-
error && /* @__PURE__ */ jsx2("p", { className: "fillo
|
|
630
|
+
error && /* @__PURE__ */ jsx2("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
564
631
|
] });
|
|
565
632
|
}
|
|
566
633
|
|
|
@@ -590,6 +657,8 @@ function formatBytes(bytes) {
|
|
|
590
657
|
}
|
|
591
658
|
function FileUploadField({ field, value, error, setValue, api, ids: providedIds }) {
|
|
592
659
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
660
|
+
const slots = useFieldSlots(field, error);
|
|
661
|
+
const strings = useStrings();
|
|
593
662
|
const schema = field;
|
|
594
663
|
const inputRef = useRef4(null);
|
|
595
664
|
const [inFlight, setInFlight] = useState4([]);
|
|
@@ -637,10 +706,11 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
637
706
|
setInFlight((prev) => prev.filter((f) => f.key !== key));
|
|
638
707
|
const current = Array.isArray(api.data[field.id]) ? api.data[field.id] : [];
|
|
639
708
|
setValue([...current, uploaded]);
|
|
640
|
-
} catch {
|
|
709
|
+
} catch (err) {
|
|
641
710
|
if (mountedRef.current) {
|
|
711
|
+
const message = err instanceof Error && err.name === "FilloError" && err.message ? err.message : "Upload failed \u2014 try again";
|
|
642
712
|
setInFlight(
|
|
643
|
-
(prev) => prev.map((f) => f.key === key ? { ...f, error:
|
|
713
|
+
(prev) => prev.map((f) => f.key === key ? { ...f, error: message } : f)
|
|
644
714
|
);
|
|
645
715
|
}
|
|
646
716
|
} finally {
|
|
@@ -662,16 +732,18 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
662
732
|
setValue(files.filter((f) => f.fileId !== fileId));
|
|
663
733
|
}
|
|
664
734
|
const describedBy = [field.description ? ids.descriptionId : null, error ? ids.errorId : null].filter(Boolean).join(" ") || void 0;
|
|
665
|
-
return /* @__PURE__ */ jsxs3("div", {
|
|
666
|
-
/* @__PURE__ */ jsxs3("label", { className: "fillo
|
|
735
|
+
return /* @__PURE__ */ jsxs3("div", { ...slots.wrapperProps(`fillo-field fillo-field--file_upload${error ? " fillo-field--error" : ""}`), children: [
|
|
736
|
+
/* @__PURE__ */ jsxs3("label", { className: slots.label, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
|
|
667
737
|
field.label,
|
|
668
|
-
!field.required && /* @__PURE__ */ jsx3("span", { className: "fillo-optional", children:
|
|
738
|
+
!field.required && /* @__PURE__ */ jsx3("span", { className: "fillo-optional", children: strings.optional })
|
|
669
739
|
] }),
|
|
670
|
-
field.description && /* @__PURE__ */ jsx3("p", { className: "fillo
|
|
740
|
+
field.description && /* @__PURE__ */ jsx3("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
671
741
|
remaining > 0 && /* @__PURE__ */ jsxs3(
|
|
672
742
|
"div",
|
|
673
743
|
{
|
|
674
|
-
|
|
744
|
+
"data-fillo": "control",
|
|
745
|
+
"data-drag-over": dragOver ? "" : void 0,
|
|
746
|
+
className: slots.control(`fillo-dropzone${dragOver ? " fillo-dropzone--over" : ""}${canUpload ? "" : " fillo-dropzone--disabled"}`),
|
|
675
747
|
onDragOver: (e) => {
|
|
676
748
|
e.preventDefault();
|
|
677
749
|
setDragOver(true);
|
|
@@ -702,7 +774,7 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
702
774
|
hidden: true,
|
|
703
775
|
multiple: maxFiles > 1,
|
|
704
776
|
accept: schema.accept?.join(","),
|
|
705
|
-
onChange: (e) => handleFiles(e.target.files)
|
|
777
|
+
onChange: (e) => canUpload && handleFiles(e.target.files)
|
|
706
778
|
}
|
|
707
779
|
),
|
|
708
780
|
canUpload ? /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
@@ -720,7 +792,16 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
720
792
|
]
|
|
721
793
|
}
|
|
722
794
|
),
|
|
723
|
-
/* @__PURE__ */ jsx3(
|
|
795
|
+
/* @__PURE__ */ jsx3(
|
|
796
|
+
"div",
|
|
797
|
+
{
|
|
798
|
+
role: "status",
|
|
799
|
+
"aria-live": "polite",
|
|
800
|
+
className: "fillo-sr-only",
|
|
801
|
+
style: { position: "absolute", left: "-9999px", width: 1, height: 1, overflow: "hidden" },
|
|
802
|
+
children: uploadStatus(inFlight, files.length)
|
|
803
|
+
}
|
|
804
|
+
),
|
|
724
805
|
(inFlight.length > 0 || files.length > 0) && /* @__PURE__ */ jsxs3("ul", { className: "fillo-files", children: [
|
|
725
806
|
files.map((f) => /* @__PURE__ */ jsxs3("li", { className: "fillo-file fillo-file--done", children: [
|
|
726
807
|
/* @__PURE__ */ jsx3("span", { className: "fillo-file-name", children: f.name }),
|
|
@@ -784,7 +865,7 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
784
865
|
f.key
|
|
785
866
|
))
|
|
786
867
|
] }),
|
|
787
|
-
error && /* @__PURE__ */ jsx3("p", { className: "fillo
|
|
868
|
+
error && /* @__PURE__ */ jsx3("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
788
869
|
] });
|
|
789
870
|
}
|
|
790
871
|
|
|
@@ -801,6 +882,18 @@ function fieldAria(field, error, ids) {
|
|
|
801
882
|
"aria-describedby": describedBy.length ? describedBy.join(" ") : void 0
|
|
802
883
|
};
|
|
803
884
|
}
|
|
885
|
+
function useControl(base, field, error) {
|
|
886
|
+
return {
|
|
887
|
+
className: useSlotClass(base, {
|
|
888
|
+
slot: "control",
|
|
889
|
+
kind: field.kind,
|
|
890
|
+
fieldId: field.id,
|
|
891
|
+
invalid: Boolean(error),
|
|
892
|
+
required: Boolean(field.required)
|
|
893
|
+
}),
|
|
894
|
+
"data-fillo": "control"
|
|
895
|
+
};
|
|
896
|
+
}
|
|
804
897
|
function isNativeControlTarget(target) {
|
|
805
898
|
return target instanceof HTMLElement && Boolean(target.closest("input, textarea, select, button"));
|
|
806
899
|
}
|
|
@@ -810,19 +903,37 @@ function FieldShell({
|
|
|
810
903
|
ids,
|
|
811
904
|
children
|
|
812
905
|
}) {
|
|
906
|
+
const state = {
|
|
907
|
+
kind: field.kind,
|
|
908
|
+
fieldId: field.id,
|
|
909
|
+
invalid: Boolean(error),
|
|
910
|
+
required: Boolean(field.required)
|
|
911
|
+
};
|
|
912
|
+
const fieldCls = useSlotClass(
|
|
913
|
+
`fillo-field fillo-field--${field.kind}${error ? " fillo-field--error" : ""}`,
|
|
914
|
+
{ slot: "field", ...state }
|
|
915
|
+
);
|
|
916
|
+
const labelCls = useSlotClass("fillo-label", { slot: "label", ...state });
|
|
917
|
+
const descriptionCls = useSlotClass("fillo-description", { slot: "fieldDescription", ...state });
|
|
918
|
+
const errorCls = useSlotClass("fillo-error", { slot: "error", ...state });
|
|
919
|
+
const strings = useStrings();
|
|
813
920
|
return /* @__PURE__ */ jsxs4(
|
|
814
921
|
"div",
|
|
815
922
|
{
|
|
816
|
-
className:
|
|
923
|
+
className: fieldCls,
|
|
924
|
+
"data-fillo": "field",
|
|
817
925
|
"data-field": field.id,
|
|
926
|
+
"data-kind": field.kind,
|
|
927
|
+
"data-invalid": error ? "" : void 0,
|
|
928
|
+
"data-required": field.required ? "" : void 0,
|
|
818
929
|
children: [
|
|
819
|
-
/* @__PURE__ */ jsxs4("label", { className: "fillo
|
|
930
|
+
/* @__PURE__ */ jsxs4("label", { className: labelCls, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
|
|
820
931
|
field.label,
|
|
821
|
-
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children:
|
|
932
|
+
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children: strings.optional })
|
|
822
933
|
] }),
|
|
823
|
-
field.description && /* @__PURE__ */ jsx4("p", { className: "fillo
|
|
934
|
+
field.description && /* @__PURE__ */ jsx4("p", { className: descriptionCls, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
824
935
|
children,
|
|
825
|
-
error && /* @__PURE__ */ jsx4("p", { className: "fillo
|
|
936
|
+
error && /* @__PURE__ */ jsx4("p", { className: errorCls, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
826
937
|
]
|
|
827
938
|
}
|
|
828
939
|
);
|
|
@@ -830,11 +941,12 @@ function FieldShell({
|
|
|
830
941
|
function TextInput({ field, value, error, setValue, ids: providedIds }) {
|
|
831
942
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
832
943
|
const type = field.kind === "email" ? "email" : field.kind === "url" ? "url" : field.kind === "number" ? "number" : field.kind === "date" ? "date" : "text";
|
|
944
|
+
const control = useControl("fillo-input", field, error);
|
|
833
945
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx4(
|
|
834
946
|
"input",
|
|
835
947
|
{
|
|
836
948
|
id: ids.inputId,
|
|
837
|
-
|
|
949
|
+
...control,
|
|
838
950
|
type,
|
|
839
951
|
value: typeof value === "string" || typeof value === "number" ? String(value) : "",
|
|
840
952
|
placeholder: field.placeholder,
|
|
@@ -845,11 +957,12 @@ function TextInput({ field, value, error, setValue, ids: providedIds }) {
|
|
|
845
957
|
}
|
|
846
958
|
function LongText({ field, value, error, setValue, ids: providedIds }) {
|
|
847
959
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
960
|
+
const control = useControl("fillo-input fillo-textarea", field, error);
|
|
848
961
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx4(
|
|
849
962
|
"textarea",
|
|
850
963
|
{
|
|
851
964
|
id: ids.inputId,
|
|
852
|
-
|
|
965
|
+
...control,
|
|
853
966
|
rows: 4,
|
|
854
967
|
value: typeof value === "string" ? value : "",
|
|
855
968
|
placeholder: field.placeholder,
|
|
@@ -898,6 +1011,9 @@ function ChoiceIcon({ icon }) {
|
|
|
898
1011
|
}
|
|
899
1012
|
function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
900
1013
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
1014
|
+
const appearance = useFilloAppearance();
|
|
1015
|
+
const strings = useStrings();
|
|
1016
|
+
const optionCls = (base, optionId, selected) => slotClass2(base, appearance, { slot: "option", kind: field.kind, fieldId: field.id, optionId, selected });
|
|
901
1017
|
const choice = field;
|
|
902
1018
|
const options = useDisplayOptions(choice);
|
|
903
1019
|
const isOtherValue = typeof value === "string" && value !== "" && !choice.options.some((o) => o.id === value);
|
|
@@ -906,7 +1022,8 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
906
1022
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs4(
|
|
907
1023
|
"div",
|
|
908
1024
|
{
|
|
909
|
-
className: "fillo-options",
|
|
1025
|
+
className: slotClass2("fillo-options", appearance, { slot: "options", kind: field.kind, fieldId: field.id }),
|
|
1026
|
+
"data-fillo": "options",
|
|
910
1027
|
role: "radiogroup",
|
|
911
1028
|
"aria-labelledby": ids.labelId,
|
|
912
1029
|
...fieldAria(field, error, ids),
|
|
@@ -915,7 +1032,13 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
915
1032
|
"label",
|
|
916
1033
|
{
|
|
917
1034
|
"data-option": opt.id,
|
|
918
|
-
|
|
1035
|
+
"data-fillo": "option",
|
|
1036
|
+
"data-selected": value === opt.id ? "" : void 0,
|
|
1037
|
+
className: optionCls(
|
|
1038
|
+
`fillo-option${opt.icon ? " fillo-option--has-icon" : ""}${value === opt.id ? " fillo-option--selected" : ""}`,
|
|
1039
|
+
opt.id,
|
|
1040
|
+
value === opt.id
|
|
1041
|
+
),
|
|
919
1042
|
onClick: (event) => {
|
|
920
1043
|
if (isNativeControlTarget(event.target)) return;
|
|
921
1044
|
event.preventDefault();
|
|
@@ -937,7 +1060,7 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
937
1060
|
}
|
|
938
1061
|
),
|
|
939
1062
|
/* @__PURE__ */ jsx4(ChoiceIcon, { icon: opt.icon }),
|
|
940
|
-
/* @__PURE__ */ jsx4("span", { className: "fillo-option-label", children: opt.label })
|
|
1063
|
+
/* @__PURE__ */ jsx4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
|
|
941
1064
|
]
|
|
942
1065
|
},
|
|
943
1066
|
opt.id
|
|
@@ -947,7 +1070,13 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
947
1070
|
/* @__PURE__ */ jsxs4(
|
|
948
1071
|
"div",
|
|
949
1072
|
{
|
|
950
|
-
|
|
1073
|
+
"data-fillo": "option",
|
|
1074
|
+
"data-selected": otherActive ? "" : void 0,
|
|
1075
|
+
className: optionCls(
|
|
1076
|
+
`fillo-option fillo-option--other${otherActive ? " fillo-option--selected fillo-option--with-other" : ""}`,
|
|
1077
|
+
"__other",
|
|
1078
|
+
otherActive
|
|
1079
|
+
),
|
|
951
1080
|
children: [
|
|
952
1081
|
/* @__PURE__ */ jsxs4("label", { className: "fillo-option-main", children: [
|
|
953
1082
|
/* @__PURE__ */ jsx4(
|
|
@@ -963,15 +1092,15 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
963
1092
|
}
|
|
964
1093
|
}
|
|
965
1094
|
),
|
|
966
|
-
/* @__PURE__ */ jsx4("span", { className: "fillo-option-label",
|
|
1095
|
+
/* @__PURE__ */ jsx4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
|
|
967
1096
|
] }),
|
|
968
1097
|
otherActive && /* @__PURE__ */ jsx4(
|
|
969
1098
|
"input",
|
|
970
1099
|
{
|
|
971
1100
|
type: "text",
|
|
972
1101
|
className: "fillo-input fillo-other-input",
|
|
973
|
-
"aria-label":
|
|
974
|
-
placeholder:
|
|
1102
|
+
"aria-label": strings.otherPrompt,
|
|
1103
|
+
placeholder: strings.otherPlaceholder,
|
|
975
1104
|
autoFocus: !isOtherValue,
|
|
976
1105
|
value: isOtherValue ? String(value) : "",
|
|
977
1106
|
onChange: (e) => setValue(e.target.value)
|
|
@@ -988,16 +1117,20 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
988
1117
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
989
1118
|
const choice = field;
|
|
990
1119
|
const options = useDisplayOptions(choice);
|
|
1120
|
+
const strings = useStrings();
|
|
991
1121
|
const optionIds = new Set(choice.options.map((o) => o.id));
|
|
992
1122
|
const selected = Array.isArray(value) ? value : [];
|
|
993
1123
|
const otherText = selected.find((v) => !optionIds.has(v));
|
|
994
1124
|
const [otherOn, setOtherOn] = useState5(otherText !== void 0);
|
|
995
1125
|
const otherActive = otherText !== void 0 || otherOn;
|
|
996
1126
|
const toggle = (id) => setValue(selected.includes(id) ? selected.filter((v) => v !== id) : [...selected, id]);
|
|
1127
|
+
const appearance = useFilloAppearance();
|
|
1128
|
+
const optionCls = (base, optionId, isSelected) => slotClass2(base, appearance, { slot: "option", kind: field.kind, fieldId: field.id, optionId, selected: isSelected });
|
|
997
1129
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs4(
|
|
998
1130
|
"div",
|
|
999
1131
|
{
|
|
1000
|
-
className: "fillo-options",
|
|
1132
|
+
className: slotClass2("fillo-options", appearance, { slot: "options", kind: field.kind, fieldId: field.id }),
|
|
1133
|
+
"data-fillo": "options",
|
|
1001
1134
|
role: "group",
|
|
1002
1135
|
"aria-labelledby": ids.labelId,
|
|
1003
1136
|
...fieldAria(field, error, ids),
|
|
@@ -1006,7 +1139,13 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1006
1139
|
"label",
|
|
1007
1140
|
{
|
|
1008
1141
|
"data-option": opt.id,
|
|
1009
|
-
|
|
1142
|
+
"data-fillo": "option",
|
|
1143
|
+
"data-selected": selected.includes(opt.id) ? "" : void 0,
|
|
1144
|
+
className: optionCls(
|
|
1145
|
+
`fillo-option${opt.icon ? " fillo-option--has-icon" : ""}${selected.includes(opt.id) ? " fillo-option--selected" : ""}`,
|
|
1146
|
+
opt.id,
|
|
1147
|
+
selected.includes(opt.id)
|
|
1148
|
+
),
|
|
1010
1149
|
onClick: (event) => {
|
|
1011
1150
|
if (isNativeControlTarget(event.target)) return;
|
|
1012
1151
|
event.preventDefault();
|
|
@@ -1023,7 +1162,7 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1023
1162
|
}
|
|
1024
1163
|
),
|
|
1025
1164
|
/* @__PURE__ */ jsx4(ChoiceIcon, { icon: opt.icon }),
|
|
1026
|
-
/* @__PURE__ */ jsx4("span", { className: "fillo-option-label", children: opt.label })
|
|
1165
|
+
/* @__PURE__ */ jsx4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
|
|
1027
1166
|
]
|
|
1028
1167
|
},
|
|
1029
1168
|
opt.id
|
|
@@ -1033,7 +1172,13 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1033
1172
|
/* @__PURE__ */ jsxs4(
|
|
1034
1173
|
"div",
|
|
1035
1174
|
{
|
|
1036
|
-
|
|
1175
|
+
"data-fillo": "option",
|
|
1176
|
+
"data-selected": otherActive ? "" : void 0,
|
|
1177
|
+
className: optionCls(
|
|
1178
|
+
`fillo-option fillo-option--other${otherActive ? " fillo-option--selected fillo-option--with-other" : ""}`,
|
|
1179
|
+
"__other",
|
|
1180
|
+
otherActive
|
|
1181
|
+
),
|
|
1037
1182
|
children: [
|
|
1038
1183
|
/* @__PURE__ */ jsxs4("label", { className: "fillo-option-main", children: [
|
|
1039
1184
|
/* @__PURE__ */ jsx4(
|
|
@@ -1052,15 +1197,15 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1052
1197
|
}
|
|
1053
1198
|
}
|
|
1054
1199
|
),
|
|
1055
|
-
/* @__PURE__ */ jsx4("span", { className: "fillo-option-label",
|
|
1200
|
+
/* @__PURE__ */ jsx4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
|
|
1056
1201
|
] }),
|
|
1057
1202
|
otherActive && /* @__PURE__ */ jsx4(
|
|
1058
1203
|
"input",
|
|
1059
1204
|
{
|
|
1060
1205
|
type: "text",
|
|
1061
1206
|
className: "fillo-input fillo-other-input",
|
|
1062
|
-
"aria-label":
|
|
1063
|
-
placeholder:
|
|
1207
|
+
"aria-label": strings.otherPrompt,
|
|
1208
|
+
placeholder: strings.otherPlaceholder,
|
|
1064
1209
|
autoFocus: otherText === void 0,
|
|
1065
1210
|
value: otherText ?? "",
|
|
1066
1211
|
onChange: (e) => {
|
|
@@ -1079,18 +1224,20 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1079
1224
|
var OTHER_SENTINEL = "__fw_other__";
|
|
1080
1225
|
function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
1081
1226
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
1227
|
+
const strings = useStrings();
|
|
1082
1228
|
const choice = field;
|
|
1083
1229
|
const options = useDisplayOptions(choice);
|
|
1084
1230
|
const isOtherValue = typeof value === "string" && value !== "" && !choice.options.some((o) => o.id === value);
|
|
1085
1231
|
const [otherOn, setOtherOn] = useState5(isOtherValue);
|
|
1086
1232
|
const otherActive = isOtherValue || value === "" && otherOn;
|
|
1233
|
+
const control = useControl("fillo-input fillo-select", field, error);
|
|
1087
1234
|
return /* @__PURE__ */ jsxs4(FieldShell, { field, error, ids, children: [
|
|
1088
1235
|
/* @__PURE__ */ jsxs4("div", { className: "fillo-select-wrap", children: [
|
|
1089
1236
|
/* @__PURE__ */ jsxs4(
|
|
1090
1237
|
"select",
|
|
1091
1238
|
{
|
|
1092
1239
|
id: ids.inputId,
|
|
1093
|
-
|
|
1240
|
+
...control,
|
|
1094
1241
|
...fieldAria(field, error, ids),
|
|
1095
1242
|
value: otherActive ? OTHER_SENTINEL : typeof value === "string" ? value : "",
|
|
1096
1243
|
onChange: (e) => {
|
|
@@ -1103,9 +1250,9 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1103
1250
|
}
|
|
1104
1251
|
},
|
|
1105
1252
|
children: [
|
|
1106
|
-
/* @__PURE__ */ jsx4("option", { value: "", children: field.placeholder ??
|
|
1253
|
+
/* @__PURE__ */ jsx4("option", { value: "", children: field.placeholder ?? strings.choosePlaceholder }),
|
|
1107
1254
|
options.map((opt) => /* @__PURE__ */ jsx4("option", { value: opt.id, children: opt.label }, opt.id)),
|
|
1108
|
-
choice.allowOther && /* @__PURE__ */ jsx4("option", { value: OTHER_SENTINEL, children:
|
|
1255
|
+
choice.allowOther && /* @__PURE__ */ jsx4("option", { value: OTHER_SENTINEL, children: `${strings.other}\u2026` })
|
|
1109
1256
|
]
|
|
1110
1257
|
}
|
|
1111
1258
|
),
|
|
@@ -1116,8 +1263,8 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1116
1263
|
{
|
|
1117
1264
|
type: "text",
|
|
1118
1265
|
className: "fillo-input fillo-other-input fillo-other-input--block",
|
|
1119
|
-
"aria-label":
|
|
1120
|
-
placeholder:
|
|
1266
|
+
"aria-label": strings.otherPrompt,
|
|
1267
|
+
placeholder: strings.otherPlaceholder,
|
|
1121
1268
|
autoFocus: !isOtherValue,
|
|
1122
1269
|
value: isOtherValue ? String(value) : "",
|
|
1123
1270
|
onChange: (e) => setValue(e.target.value)
|
|
@@ -1127,18 +1274,24 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1127
1274
|
}
|
|
1128
1275
|
function Checkbox({ field, value, error, setValue, ids: providedIds }) {
|
|
1129
1276
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
1277
|
+
const strings = useStrings();
|
|
1278
|
+
const appearance = useFilloAppearance();
|
|
1130
1279
|
const checkbox = field;
|
|
1131
1280
|
if (checkbox.appearance === "toggle") {
|
|
1132
1281
|
return /* @__PURE__ */ jsxs4(
|
|
1133
1282
|
"div",
|
|
1134
1283
|
{
|
|
1135
1284
|
className: `fillo-field fillo-field--checkbox fillo-field--toggle${error ? " fillo-field--error" : ""}`,
|
|
1285
|
+
"data-fillo": "field",
|
|
1136
1286
|
"data-field": field.id,
|
|
1287
|
+
"data-kind": field.kind,
|
|
1288
|
+
"data-invalid": error ? "" : void 0,
|
|
1289
|
+
"data-required": field.required ? "" : void 0,
|
|
1137
1290
|
children: [
|
|
1138
|
-
/* @__PURE__ */ jsxs4("label", { className: "fillo-toggle", children: [
|
|
1139
|
-
/* @__PURE__ */ jsx4("span", { className: "fillo-toggle-copy", children: /* @__PURE__ */ jsxs4("span", { className: "fillo-option-label", children: [
|
|
1291
|
+
/* @__PURE__ */ jsxs4("label", { className: "fillo-toggle", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
|
|
1292
|
+
/* @__PURE__ */ jsx4("span", { className: "fillo-toggle-copy", children: /* @__PURE__ */ jsxs4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: [
|
|
1140
1293
|
field.label,
|
|
1141
|
-
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children:
|
|
1294
|
+
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children: strings.optional })
|
|
1142
1295
|
] }) }),
|
|
1143
1296
|
/* @__PURE__ */ jsx4(
|
|
1144
1297
|
"input",
|
|
@@ -1165,9 +1318,13 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1165
1318
|
"div",
|
|
1166
1319
|
{
|
|
1167
1320
|
className: `fillo-field fillo-field--checkbox${error ? " fillo-field--error" : ""}`,
|
|
1321
|
+
"data-fillo": "field",
|
|
1168
1322
|
"data-field": field.id,
|
|
1323
|
+
"data-kind": field.kind,
|
|
1324
|
+
"data-invalid": error ? "" : void 0,
|
|
1325
|
+
"data-required": field.required ? "" : void 0,
|
|
1169
1326
|
children: [
|
|
1170
|
-
/* @__PURE__ */ jsxs4("label", { className: "fillo-option", children: [
|
|
1327
|
+
/* @__PURE__ */ jsxs4("label", { className: "fillo-option", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
|
|
1171
1328
|
/* @__PURE__ */ jsx4(
|
|
1172
1329
|
"input",
|
|
1173
1330
|
{
|
|
@@ -1179,9 +1336,9 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1179
1336
|
...fieldAria(field, error, ids)
|
|
1180
1337
|
}
|
|
1181
1338
|
),
|
|
1182
|
-
/* @__PURE__ */ jsxs4("span", { className: "fillo-option-label", children: [
|
|
1339
|
+
/* @__PURE__ */ jsxs4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: [
|
|
1183
1340
|
field.label,
|
|
1184
|
-
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children:
|
|
1341
|
+
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children: strings.optional })
|
|
1185
1342
|
] })
|
|
1186
1343
|
] }),
|
|
1187
1344
|
field.description && /* @__PURE__ */ jsx4("p", { className: "fillo-description", id: ids.descriptionId, children: field.description }),
|
|
@@ -1222,10 +1379,12 @@ function Rating({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1222
1379
|
const steps = Array.from({ length: max }, (_, i) => i + 1);
|
|
1223
1380
|
const selected = current > 0 ? current : null;
|
|
1224
1381
|
const tabbable = rovingIndex(steps, selected);
|
|
1382
|
+
const appearance = useFilloAppearance();
|
|
1383
|
+
const control = useControl("fillo-rating", field, error);
|
|
1225
1384
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx4(
|
|
1226
1385
|
"div",
|
|
1227
1386
|
{
|
|
1228
|
-
|
|
1387
|
+
...control,
|
|
1229
1388
|
role: "radiogroup",
|
|
1230
1389
|
"aria-labelledby": ids.labelId,
|
|
1231
1390
|
...fieldAria(field, error, ids),
|
|
@@ -1234,7 +1393,14 @@ function Rating({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1234
1393
|
"button",
|
|
1235
1394
|
{
|
|
1236
1395
|
type: "button",
|
|
1237
|
-
|
|
1396
|
+
"data-fillo": "option",
|
|
1397
|
+
"data-selected": n <= current ? "" : void 0,
|
|
1398
|
+
className: slotClass2(`fillo-star${n <= current ? " fillo-star--active" : ""}`, appearance, {
|
|
1399
|
+
slot: "option",
|
|
1400
|
+
kind: field.kind,
|
|
1401
|
+
fieldId: field.id,
|
|
1402
|
+
selected: n <= current
|
|
1403
|
+
}),
|
|
1238
1404
|
role: "radio",
|
|
1239
1405
|
"aria-label": `${n} of ${max}`,
|
|
1240
1406
|
"aria-checked": n === current,
|
|
@@ -1264,11 +1430,13 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1264
1430
|
field.description ? ids.descriptionId : null,
|
|
1265
1431
|
error ? ids.errorId : null
|
|
1266
1432
|
].filter(Boolean).join(" ") || void 0;
|
|
1433
|
+
const appearance = useFilloAppearance();
|
|
1434
|
+
const control = useControl("fillo-scale", field, error);
|
|
1267
1435
|
return /* @__PURE__ */ jsxs4(FieldShell, { field, error, ids, children: [
|
|
1268
1436
|
/* @__PURE__ */ jsx4(
|
|
1269
1437
|
"div",
|
|
1270
1438
|
{
|
|
1271
|
-
|
|
1439
|
+
...control,
|
|
1272
1440
|
role: "radiogroup",
|
|
1273
1441
|
"aria-labelledby": ids.labelId,
|
|
1274
1442
|
"aria-invalid": error ? true : void 0,
|
|
@@ -1279,7 +1447,14 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1279
1447
|
"button",
|
|
1280
1448
|
{
|
|
1281
1449
|
type: "button",
|
|
1282
|
-
|
|
1450
|
+
"data-fillo": "option",
|
|
1451
|
+
"data-selected": value === n ? "" : void 0,
|
|
1452
|
+
className: slotClass2(`fillo-scale-step${value === n ? " fillo-scale-step--active" : ""}`, appearance, {
|
|
1453
|
+
slot: "option",
|
|
1454
|
+
kind: field.kind,
|
|
1455
|
+
fieldId: field.id,
|
|
1456
|
+
selected: value === n
|
|
1457
|
+
}),
|
|
1283
1458
|
role: "radio",
|
|
1284
1459
|
"aria-label": String(n),
|
|
1285
1460
|
"aria-checked": value === n,
|
|
@@ -1414,30 +1589,6 @@ function ContentRenderer({ block }) {
|
|
|
1414
1589
|
return /* @__PURE__ */ jsx4("hr", { className: "fillo-divider" });
|
|
1415
1590
|
}
|
|
1416
1591
|
}
|
|
1417
|
-
function shouldAutoSubmit(field, value, api) {
|
|
1418
|
-
if (api.form.settings.submitMode !== "auto") return false;
|
|
1419
|
-
if (!api.isLastPage || api.status !== "idle" || api.uploading) return false;
|
|
1420
|
-
const hasDiscreteAnswer = (() => {
|
|
1421
|
-
switch (field.kind) {
|
|
1422
|
-
case "select":
|
|
1423
|
-
case "dropdown":
|
|
1424
|
-
return typeof value === "string" && field.options.some((option) => option.id === value);
|
|
1425
|
-
case "checkbox":
|
|
1426
|
-
return value === true;
|
|
1427
|
-
case "rating":
|
|
1428
|
-
case "linear_scale":
|
|
1429
|
-
return typeof value === "number";
|
|
1430
|
-
default:
|
|
1431
|
-
return false;
|
|
1432
|
-
}
|
|
1433
|
-
})();
|
|
1434
|
-
if (!hasDiscreteAnswer) return false;
|
|
1435
|
-
if (value === api.data[field.id]) return false;
|
|
1436
|
-
const nextData = { ...api.data, [field.id]: value };
|
|
1437
|
-
const interactive = visibleFields(api.form, nextData).filter((f) => f.kind !== "hidden");
|
|
1438
|
-
if (interactive.length !== 1 || interactive[0]?.id !== field.id) return false;
|
|
1439
|
-
return validateResponse(api.form, nextData).ok;
|
|
1440
|
-
}
|
|
1441
1592
|
function blockHasPiping(block) {
|
|
1442
1593
|
const has = (s) => typeof s === "string" && s.includes("{{");
|
|
1443
1594
|
switch (block.kind) {
|
|
@@ -1497,7 +1648,15 @@ function BlockRendererInner({ block, api, components, customComponents }) {
|
|
|
1497
1648
|
error: api.errors[block.id],
|
|
1498
1649
|
setValue: (v) => {
|
|
1499
1650
|
api.setValue(block.id, v);
|
|
1500
|
-
|
|
1651
|
+
const ctx = {
|
|
1652
|
+
form: api.form,
|
|
1653
|
+
data: api.data,
|
|
1654
|
+
status: api.status,
|
|
1655
|
+
isLastPage: api.isLastPage,
|
|
1656
|
+
uploading: api.uploading
|
|
1657
|
+
};
|
|
1658
|
+
if (shouldAutoSubmit(block, v, ctx)) void api.submit().catch(() => {
|
|
1659
|
+
});
|
|
1501
1660
|
},
|
|
1502
1661
|
api,
|
|
1503
1662
|
ids
|
|
@@ -1511,8 +1670,13 @@ function FormField({
|
|
|
1511
1670
|
customComponents
|
|
1512
1671
|
}) {
|
|
1513
1672
|
const api = useFillo();
|
|
1673
|
+
const visibleIds = useMemo4(
|
|
1674
|
+
() => new Set(visibleFields(api.form, api.data).map((f) => f.id)),
|
|
1675
|
+
[api.form, api.data]
|
|
1676
|
+
);
|
|
1514
1677
|
const block = api.form.pages.flatMap((p) => p.blocks).find((b) => b.id === id);
|
|
1515
1678
|
if (!block) return null;
|
|
1679
|
+
if (isField(block) && !visibleIds.has(block.id)) return null;
|
|
1516
1680
|
return /* @__PURE__ */ jsx4(
|
|
1517
1681
|
BlockRenderer,
|
|
1518
1682
|
{
|
|
@@ -1566,18 +1730,25 @@ function focusFirstInvalid(formEl) {
|
|
|
1566
1730
|
if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
|
|
1567
1731
|
else setTimeout(run, 0);
|
|
1568
1732
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1733
|
+
var HP_STYLE = {
|
|
1734
|
+
position: "absolute",
|
|
1735
|
+
left: "-9999px",
|
|
1736
|
+
top: "auto",
|
|
1737
|
+
width: "1px",
|
|
1738
|
+
height: "1px",
|
|
1739
|
+
opacity: 0,
|
|
1740
|
+
overflow: "hidden",
|
|
1741
|
+
pointerEvents: "none"
|
|
1742
|
+
};
|
|
1743
|
+
function assertHpHidden(el) {
|
|
1744
|
+
if (!el) return;
|
|
1745
|
+
for (const [prop, value] of Object.entries(HP_STYLE)) {
|
|
1746
|
+
el.style[prop] = String(value);
|
|
1747
|
+
}
|
|
1577
1748
|
}
|
|
1578
1749
|
function FilloForm(props) {
|
|
1579
1750
|
const { form, formId } = props;
|
|
1580
|
-
const client =
|
|
1751
|
+
const client = useMemo5(
|
|
1581
1752
|
() => props.client ?? (formId ? createClient() : void 0),
|
|
1582
1753
|
[props.client, formId]
|
|
1583
1754
|
);
|
|
@@ -1589,7 +1760,7 @@ function FilloForm(props) {
|
|
|
1589
1760
|
const codeForm = isCodeForm2(form) ? form : null;
|
|
1590
1761
|
const inlineSchema = codeForm ? codeForm.schema : form;
|
|
1591
1762
|
const onError = props.onError;
|
|
1592
|
-
const syncedFormId = useCodeFormSync(form, client, onError);
|
|
1763
|
+
const { formId: syncedFormId, status: syncedStatus } = useCodeFormSync(form, client, onError);
|
|
1593
1764
|
const onErrorRef = useRef5(onError);
|
|
1594
1765
|
useEffect7(() => {
|
|
1595
1766
|
onErrorRef.current = onError;
|
|
@@ -1616,24 +1787,31 @@ function FilloForm(props) {
|
|
|
1616
1787
|
cancelled = true;
|
|
1617
1788
|
};
|
|
1618
1789
|
}, [needsFetch, client, formId]);
|
|
1790
|
+
const strings = resolveStrings(props.strings);
|
|
1619
1791
|
const rawSchema = inlineSchema ?? fetched;
|
|
1620
|
-
const normalized =
|
|
1792
|
+
const normalized = useMemo5(() => rawSchema ? normalizeFormSchema(rawSchema) : null, [rawSchema]);
|
|
1621
1793
|
const schema = normalized?.ok ? normalized.schema : null;
|
|
1622
|
-
const theme = props.theme ?? codeForm?.theme ?? fetchedTheme ?? void 0;
|
|
1794
|
+
const theme = props.appearance?.theme ?? props.theme ?? codeForm?.theme ?? fetchedTheme ?? void 0;
|
|
1623
1795
|
if (normalized && !normalized.ok) {
|
|
1624
1796
|
const error = new FilloError2(`This form could not be rendered: ${normalized.error}`, 422);
|
|
1625
1797
|
if (props.renderError) return /* @__PURE__ */ jsx5(Fragment2, { children: props.renderError(error) });
|
|
1626
|
-
return /* @__PURE__ */ jsx5("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children:
|
|
1798
|
+
return /* @__PURE__ */ jsx5("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: strings.renderFailed });
|
|
1627
1799
|
}
|
|
1628
1800
|
if (loadError) {
|
|
1629
1801
|
if (props.renderError) {
|
|
1630
1802
|
return /* @__PURE__ */ jsx5(Fragment2, { children: props.renderError(loadError) });
|
|
1631
1803
|
}
|
|
1632
|
-
const message = loadError.status === 404 ?
|
|
1804
|
+
const message = loadError.status === 404 ? strings.loadFailedNotFound : loadError.status === 0 ? strings.loadFailedNetwork : strings.loadFailed;
|
|
1633
1805
|
return /* @__PURE__ */ jsx5("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: message });
|
|
1634
1806
|
}
|
|
1635
1807
|
if (closed) {
|
|
1636
|
-
return /* @__PURE__ */ jsx5("div", { className: `fillo-form fillo-form--closed ${props.className ?? ""}`, ...themeProps(theme), children: /* @__PURE__ */ jsx5("p", { className: "fillo-closed", children:
|
|
1808
|
+
return /* @__PURE__ */ jsx5("div", { className: `fillo-form fillo-form--closed ${props.className ?? ""}`, ...themeProps(theme), children: /* @__PURE__ */ jsx5("p", { className: "fillo-closed", children: strings.closed }) });
|
|
1809
|
+
}
|
|
1810
|
+
const notPublished = Boolean(codeForm) && syncedStatus === "draft" && !props.skipValidation;
|
|
1811
|
+
if (notPublished && !isDevEnv()) {
|
|
1812
|
+
const error = new FilloError2("This form isn't published yet.", 403);
|
|
1813
|
+
if (props.renderError) return /* @__PURE__ */ jsx5(Fragment2, { children: props.renderError(error) });
|
|
1814
|
+
return /* @__PURE__ */ jsx5("div", { className: `fillo-form fillo-form--closed ${props.className ?? ""}`, ...themeProps(theme), children: /* @__PURE__ */ jsx5("p", { className: "fillo-closed", children: strings.notLive }) });
|
|
1637
1815
|
}
|
|
1638
1816
|
if (!schema) {
|
|
1639
1817
|
return /* @__PURE__ */ jsxs5(
|
|
@@ -1661,7 +1839,8 @@ function FilloForm(props) {
|
|
|
1661
1839
|
formId: syncedFormId ?? formId,
|
|
1662
1840
|
poweredBy,
|
|
1663
1841
|
brandLabel: fetchedBranding?.label,
|
|
1664
|
-
brandHref: fetchedBranding?.href
|
|
1842
|
+
brandHref: fetchedBranding?.href,
|
|
1843
|
+
draftNotice: notPublished
|
|
1665
1844
|
}
|
|
1666
1845
|
);
|
|
1667
1846
|
}
|
|
@@ -1679,10 +1858,25 @@ function DevNoClientNotice() {
|
|
|
1679
1858
|
" to collect them in Fillo, or forward them with webhooks. (This notice only appears in development.)"
|
|
1680
1859
|
] });
|
|
1681
1860
|
}
|
|
1861
|
+
function DevDraftNotice() {
|
|
1862
|
+
return /* @__PURE__ */ jsxs5("div", { className: "fillo-devwarning", role: "alert", children: [
|
|
1863
|
+
/* @__PURE__ */ jsx5("strong", { children: "Draft form." }),
|
|
1864
|
+
" It renders here, but it won\u2019t accept responses until you publish it in your Fillo dashboard. (This notice only appears in development.)"
|
|
1865
|
+
] });
|
|
1866
|
+
}
|
|
1682
1867
|
function ResolvedForm(props) {
|
|
1683
1868
|
const { schema, theme } = props;
|
|
1684
1869
|
const instanceId = useId2().replace(/:/g, "");
|
|
1685
1870
|
const hpRef = useRef5("");
|
|
1871
|
+
const codeForm = isCodeForm2(props.form) ? props.form : null;
|
|
1872
|
+
const client = props.client;
|
|
1873
|
+
const resolveFormId = useMemo5(
|
|
1874
|
+
() => codeForm && client?.key ? (
|
|
1875
|
+
// bypassCache: a stale cached formId must not be trusted at submit time.
|
|
1876
|
+
() => syncCodeForm2(client, codeForm, { bypassCache: true }).then((r) => r.formId)
|
|
1877
|
+
) : void 0,
|
|
1878
|
+
[codeForm, client]
|
|
1879
|
+
);
|
|
1686
1880
|
const api = useFilloController({
|
|
1687
1881
|
form: schema,
|
|
1688
1882
|
formId: props.formId,
|
|
@@ -1691,9 +1885,13 @@ function ResolvedForm(props) {
|
|
|
1691
1885
|
onChange: props.onChange,
|
|
1692
1886
|
onSubmitted: props.onSubmitted,
|
|
1693
1887
|
skipValidation: props.skipValidation,
|
|
1694
|
-
getHoneypot: () => hpRef.current
|
|
1888
|
+
getHoneypot: () => hpRef.current,
|
|
1889
|
+
// Framed renderer: Fillo draws the layout (and badge), so the surface is
|
|
1890
|
+
// "default", never the bare-engine "headless".
|
|
1891
|
+
surface: "default",
|
|
1892
|
+
resolveFormId
|
|
1695
1893
|
});
|
|
1696
|
-
const themed =
|
|
1894
|
+
const themed = useMemo5(() => themeProps(theme), [theme]);
|
|
1697
1895
|
const settings = schema.settings;
|
|
1698
1896
|
const submitted = api.status === "submitted";
|
|
1699
1897
|
useEffect7(() => {
|
|
@@ -1712,6 +1910,8 @@ function ResolvedForm(props) {
|
|
|
1712
1910
|
prevPageIndex.current = api.pageIndex;
|
|
1713
1911
|
blocksRef.current?.focus();
|
|
1714
1912
|
}, [api.pageIndex]);
|
|
1913
|
+
const appearance = props.appearance;
|
|
1914
|
+
const strings = resolveStrings(props.strings);
|
|
1715
1915
|
if (api.status === "submitted") {
|
|
1716
1916
|
return /* @__PURE__ */ jsxs5(
|
|
1717
1917
|
"div",
|
|
@@ -1720,14 +1920,27 @@ function ResolvedForm(props) {
|
|
|
1720
1920
|
tabIndex: -1,
|
|
1721
1921
|
role: "status",
|
|
1722
1922
|
"aria-live": "polite",
|
|
1723
|
-
className:
|
|
1923
|
+
className: slotClass3(
|
|
1924
|
+
`fillo-form fillo-form--success ${props.className ?? ""}`,
|
|
1925
|
+
appearance,
|
|
1926
|
+
{ slot: "root", status: "submitted" }
|
|
1927
|
+
),
|
|
1928
|
+
"data-fillo": "root",
|
|
1929
|
+
"data-state": "submitted",
|
|
1724
1930
|
...themed,
|
|
1725
1931
|
children: [
|
|
1726
|
-
props.renderSuccess ? props.renderSuccess() : /* @__PURE__ */ jsxs5(
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1932
|
+
props.renderSuccess ? props.renderSuccess() : /* @__PURE__ */ jsxs5(
|
|
1933
|
+
"div",
|
|
1934
|
+
{
|
|
1935
|
+
className: slotClass3("fillo-success", appearance, { slot: "success", status: "submitted" }),
|
|
1936
|
+
"data-fillo": "success",
|
|
1937
|
+
children: [
|
|
1938
|
+
/* @__PURE__ */ jsx5("div", { className: "fillo-success-mark", "aria-hidden": "true", children: "\u2713" }),
|
|
1939
|
+
/* @__PURE__ */ jsx5("h2", { className: "fillo-success-title", children: settings.successTitle ?? strings.successTitle }),
|
|
1940
|
+
/* @__PURE__ */ jsx5("p", { className: "fillo-success-message", children: settings.successMessage ?? strings.successMessage })
|
|
1941
|
+
]
|
|
1942
|
+
}
|
|
1943
|
+
),
|
|
1731
1944
|
props.poweredBy && /* @__PURE__ */ jsx5(PoweredBy, { label: props.brandLabel, href: props.brandHref })
|
|
1732
1945
|
]
|
|
1733
1946
|
}
|
|
@@ -1737,27 +1950,38 @@ function ResolvedForm(props) {
|
|
|
1737
1950
|
const autoSubmit = settings.submitMode === "auto";
|
|
1738
1951
|
const showPrimaryButton = !autoSubmit || !api.isLastPage || needsExplicitSubmit(visibleFields2(api.form, api.data));
|
|
1739
1952
|
const showFooter = showPrimaryButton || multiPage && !api.isFirstPage;
|
|
1740
|
-
return /* @__PURE__ */ jsx5(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx5(FilloInstanceIdContext.Provider, { value: instanceId, children: /* @__PURE__ */ jsxs5(
|
|
1953
|
+
return /* @__PURE__ */ jsx5(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx5(FilloStringsContext.Provider, { value: strings, children: /* @__PURE__ */ jsx5(FilloAppearanceContext.Provider, { value: appearance, children: /* @__PURE__ */ jsx5(FilloInstanceIdContext.Provider, { value: instanceId, children: /* @__PURE__ */ jsxs5(
|
|
1741
1954
|
"form",
|
|
1742
1955
|
{
|
|
1743
|
-
className: `fillo-form ${props.className ?? ""}`,
|
|
1956
|
+
className: slotClass3(`fillo-form ${props.className ?? ""}`, appearance, {
|
|
1957
|
+
slot: "root",
|
|
1958
|
+
status: api.status
|
|
1959
|
+
}),
|
|
1960
|
+
"data-fillo": "root",
|
|
1961
|
+
"data-state": api.status,
|
|
1962
|
+
"data-page": api.pageIndex + 1,
|
|
1963
|
+
"data-last-page": api.isLastPage ? "" : void 0,
|
|
1744
1964
|
...themed,
|
|
1745
1965
|
noValidate: true,
|
|
1746
1966
|
onSubmit: (e) => {
|
|
1747
1967
|
e.preventDefault();
|
|
1748
1968
|
const formEl = e.currentTarget;
|
|
1749
|
-
if (api.isLastPage)
|
|
1750
|
-
|
|
1969
|
+
if (api.isLastPage) {
|
|
1970
|
+
void api.submit().then(() => focusFirstInvalid(formEl)).catch(() => {
|
|
1971
|
+
});
|
|
1972
|
+
} else {
|
|
1751
1973
|
api.next();
|
|
1752
1974
|
focusFirstInvalid(formEl);
|
|
1753
1975
|
}
|
|
1754
1976
|
},
|
|
1755
1977
|
children: [
|
|
1756
1978
|
!props.client && !props.skipValidation && isDevEnv() && /* @__PURE__ */ jsx5(DevNoClientNotice, {}),
|
|
1979
|
+
props.draftNotice && isDevEnv() && /* @__PURE__ */ jsx5(DevDraftNotice, {}),
|
|
1757
1980
|
multiPage && settings.showProgress !== false && /* @__PURE__ */ jsx5(
|
|
1758
1981
|
"div",
|
|
1759
1982
|
{
|
|
1760
|
-
className: "fillo-progress-track",
|
|
1983
|
+
className: slotClass3("fillo-progress-track", appearance, { slot: "progress" }),
|
|
1984
|
+
"data-fillo": "progress",
|
|
1761
1985
|
role: "progressbar",
|
|
1762
1986
|
"aria-valuemin": 0,
|
|
1763
1987
|
"aria-valuemax": api.pageCount,
|
|
@@ -1765,18 +1989,26 @@ function ResolvedForm(props) {
|
|
|
1765
1989
|
children: /* @__PURE__ */ jsx5(
|
|
1766
1990
|
"div",
|
|
1767
1991
|
{
|
|
1768
|
-
className: "fillo-progress-fill",
|
|
1769
|
-
|
|
1992
|
+
className: slotClass3("fillo-progress-fill", appearance, { slot: "progressFill" }),
|
|
1993
|
+
"data-fillo": "progressFill",
|
|
1994
|
+
style: { "--fillo-progress-value": `${(api.pageIndex + 1) / api.pageCount * 100}%` }
|
|
1770
1995
|
}
|
|
1771
1996
|
)
|
|
1772
1997
|
}
|
|
1773
1998
|
),
|
|
1774
|
-
api.pageIndex === 0 && props.showTitle !== false && /* @__PURE__ */ jsxs5("header", { className: "fillo-header", children: [
|
|
1775
|
-
/* @__PURE__ */ jsx5("h1", { className: "fillo-title", children: schema.title }),
|
|
1776
|
-
schema.description && /* @__PURE__ */ jsx5(
|
|
1999
|
+
api.pageIndex === 0 && props.showTitle !== false && /* @__PURE__ */ jsxs5("header", { className: slotClass3("fillo-header", appearance, { slot: "header" }), "data-fillo": "header", children: [
|
|
2000
|
+
/* @__PURE__ */ jsx5("h1", { className: slotClass3("fillo-title", appearance, { slot: "title" }), "data-fillo": "title", children: schema.title }),
|
|
2001
|
+
schema.description && /* @__PURE__ */ jsx5(
|
|
2002
|
+
"p",
|
|
2003
|
+
{
|
|
2004
|
+
className: slotClass3("fillo-form-description", appearance, { slot: "description" }),
|
|
2005
|
+
"data-fillo": "description",
|
|
2006
|
+
children: schema.description
|
|
2007
|
+
}
|
|
2008
|
+
)
|
|
1777
2009
|
] }),
|
|
1778
|
-
multiPage && api.page.title && api.pageIndex > 0 && /* @__PURE__ */ jsx5("h2", { className: "fillo-page-title", children: api.page.title }),
|
|
1779
|
-
/* @__PURE__ */ jsx5("div", { className: "fillo-blocks", ref: blocksRef, tabIndex: -1, children: api.blocks.map((block) => /* @__PURE__ */ jsx5(
|
|
2010
|
+
multiPage && api.page.title && api.pageIndex > 0 && /* @__PURE__ */ jsx5("h2", { className: slotClass3("fillo-page-title", appearance, { slot: "pageTitle" }), "data-fillo": "pageTitle", children: api.page.title }),
|
|
2011
|
+
/* @__PURE__ */ jsx5("div", { className: slotClass3("fillo-blocks", appearance, { slot: "blocks" }), "data-fillo": "blocks", ref: blocksRef, tabIndex: -1, children: api.blocks.map((block) => /* @__PURE__ */ jsx5(
|
|
1780
2012
|
BlockRenderer,
|
|
1781
2013
|
{
|
|
1782
2014
|
block,
|
|
@@ -1796,70 +2028,162 @@ function ResolvedForm(props) {
|
|
|
1796
2028
|
autoComplete: "off",
|
|
1797
2029
|
"aria-hidden": "true",
|
|
1798
2030
|
defaultValue: "",
|
|
2031
|
+
style: HP_STYLE,
|
|
2032
|
+
ref: assertHpHidden,
|
|
1799
2033
|
onChange: (e) => {
|
|
1800
2034
|
hpRef.current = e.target.value;
|
|
1801
2035
|
}
|
|
1802
2036
|
}
|
|
1803
2037
|
),
|
|
1804
|
-
api.status === "error" && /* @__PURE__ */ jsx5("p", { className: "fillo-submit-error", role: "alert", children:
|
|
1805
|
-
showFooter && /* @__PURE__ */ jsxs5("footer", { className: "fillo-footer", children: [
|
|
1806
|
-
multiPage && !api.isFirstPage && /* @__PURE__ */ jsx5(
|
|
2038
|
+
(api.status === "error" || api.submitError) && /* @__PURE__ */ jsx5("p", { className: "fillo-submit-error", role: "alert", children: api.submitError ?? strings.submitFailed }),
|
|
2039
|
+
showFooter && /* @__PURE__ */ jsxs5("footer", { className: slotClass3("fillo-footer", appearance, { slot: "footer" }), "data-fillo": "footer", children: [
|
|
2040
|
+
multiPage && !api.isFirstPage && /* @__PURE__ */ jsx5(
|
|
2041
|
+
"button",
|
|
2042
|
+
{
|
|
2043
|
+
type: "button",
|
|
2044
|
+
className: slotClass3("fillo-button fillo-button--ghost", appearance, { slot: "button", variant: "ghost" }),
|
|
2045
|
+
"data-fillo": "button",
|
|
2046
|
+
onClick: api.back,
|
|
2047
|
+
children: strings.back
|
|
2048
|
+
}
|
|
2049
|
+
),
|
|
1807
2050
|
showPrimaryButton && /* @__PURE__ */ jsx5(
|
|
1808
2051
|
"button",
|
|
1809
2052
|
{
|
|
1810
2053
|
type: "submit",
|
|
1811
|
-
className: "fillo-button fillo-button--primary",
|
|
2054
|
+
className: slotClass3("fillo-button fillo-button--primary", appearance, { slot: "button", variant: "primary" }),
|
|
2055
|
+
"data-fillo": "button",
|
|
1812
2056
|
disabled: api.status === "submitting" || api.uploading,
|
|
1813
|
-
children: api.uploading ?
|
|
2057
|
+
children: api.uploading ? strings.uploading : api.status === "submitting" ? strings.submitting : api.isLastPage ? settings.submitLabel ?? strings.submit : strings.next
|
|
1814
2058
|
}
|
|
1815
2059
|
)
|
|
1816
2060
|
] }),
|
|
1817
2061
|
props.poweredBy && /* @__PURE__ */ jsx5(PoweredBy, { label: props.brandLabel, href: props.brandHref })
|
|
1818
2062
|
]
|
|
1819
2063
|
}
|
|
1820
|
-
) }) });
|
|
2064
|
+
) }) }) }) });
|
|
1821
2065
|
}
|
|
1822
2066
|
|
|
1823
|
-
// src/
|
|
1824
|
-
import {
|
|
1825
|
-
import {
|
|
2067
|
+
// src/jsx.tsx
|
|
2068
|
+
import { useRef as useRef6 } from "react";
|
|
2069
|
+
import {
|
|
2070
|
+
codeFormFromJsx,
|
|
2071
|
+
FilloJsxError,
|
|
2072
|
+
JSX_BLOCK_COMPONENTS
|
|
2073
|
+
} from "@usefillo/core";
|
|
1826
2074
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1827
|
-
|
|
2075
|
+
var B = JSX_BLOCK_COMPONENTS;
|
|
2076
|
+
var isDevEnv2 = () => typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
2077
|
+
function JsxForm(props) {
|
|
2078
|
+
const { id, title, description, settings, theme, client, children, ...rest } = props;
|
|
2079
|
+
const compiled = codeFormFromJsx({ id, title, description, settings, theme }, children);
|
|
2080
|
+
const json = JSON.stringify(compiled.schema) + JSON.stringify(compiled.theme ?? null);
|
|
2081
|
+
const memo2 = useRef6(null);
|
|
2082
|
+
if (memo2.current && memo2.current.json !== json && isDevEnv2()) {
|
|
2083
|
+
console.warn(
|
|
2084
|
+
`[fillo] "${id}": form structure changed between renders. If you just edited the form, ignore this. Otherwise: conditional questions are visibleIf={when(\u2026)\u2026}, never {cond && <Fillo.\u2026/>} \u2014 a per-visitor schema churns drafts in your workspace.`
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
if (!memo2.current || memo2.current.json !== json) {
|
|
2088
|
+
memo2.current = { json, form: compiled };
|
|
2089
|
+
}
|
|
2090
|
+
return /* @__PURE__ */ jsx6(FilloForm, { form: memo2.current.form, client, ...rest });
|
|
2091
|
+
}
|
|
2092
|
+
function defineFormFromJsx(element) {
|
|
2093
|
+
if (!element || element.type !== JsxForm) {
|
|
2094
|
+
throw new FilloJsxError(
|
|
2095
|
+
"NON_FILLO_CHILD",
|
|
2096
|
+
"Fillo.defineForm() takes a <Fillo.Form> element: Fillo.defineForm(<Fillo.Form id=\u2026>\u2026</Fillo.Form>)"
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
const { id, title, description, settings, theme, children } = element.props;
|
|
2100
|
+
return codeFormFromJsx({ id, title, description, settings, theme }, children);
|
|
2101
|
+
}
|
|
2102
|
+
var Fillo = {
|
|
2103
|
+
Form: JsxForm,
|
|
2104
|
+
/** Compile a <Fillo.Form> element to a CodeForm at module scope — the same
|
|
2105
|
+
* value FilloProvider (headless) and the CLI consume. */
|
|
2106
|
+
defineForm: defineFormFromJsx,
|
|
2107
|
+
Text: B.Text,
|
|
2108
|
+
LongText: B.LongText,
|
|
2109
|
+
Email: B.Email,
|
|
2110
|
+
Url: B.Url,
|
|
2111
|
+
Phone: B.Phone,
|
|
2112
|
+
Number: B.Number,
|
|
2113
|
+
Select: B.Select,
|
|
2114
|
+
MultiSelect: B.MultiSelect,
|
|
2115
|
+
Dropdown: B.Dropdown,
|
|
2116
|
+
Checkbox: B.Checkbox,
|
|
2117
|
+
Rating: B.Rating,
|
|
2118
|
+
Scale: B.Scale,
|
|
2119
|
+
Ranking: B.Ranking,
|
|
2120
|
+
Matrix: B.Matrix,
|
|
2121
|
+
Signature: B.Signature,
|
|
2122
|
+
Date: B.Date,
|
|
2123
|
+
FileUpload: B.FileUpload,
|
|
2124
|
+
Hidden: B.Hidden,
|
|
2125
|
+
Custom: B.Custom,
|
|
2126
|
+
Heading: B.Heading,
|
|
2127
|
+
Paragraph: B.Paragraph,
|
|
2128
|
+
Divider: B.Divider,
|
|
2129
|
+
Page: B.Page,
|
|
2130
|
+
Option: B.Option
|
|
2131
|
+
};
|
|
2132
|
+
|
|
2133
|
+
// src/provider.tsx
|
|
2134
|
+
import { useId as useId3, useMemo as useMemo6 } from "react";
|
|
2135
|
+
import { FilloError as FilloError3, normalizeFormSchema as normalizeFormSchema2, resolveStrings as resolveStrings2 } from "@usefillo/core";
|
|
2136
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2137
|
+
function FilloProvider({ children, form, formId, client, appearance, strings, ...options }) {
|
|
1828
2138
|
const instanceId = useId3().replace(/:/g, "");
|
|
1829
2139
|
const codeForm = isCodeForm2(form) ? form : null;
|
|
1830
2140
|
const rawSchema = codeForm ? codeForm.schema : form;
|
|
1831
|
-
const schemaKey =
|
|
1832
|
-
const normalized =
|
|
2141
|
+
const schemaKey = useMemo6(() => JSON.stringify(rawSchema), [rawSchema]);
|
|
2142
|
+
const normalized = useMemo6(() => normalizeFormSchema2(rawSchema), [rawSchema, schemaKey]);
|
|
1833
2143
|
if (!normalized.ok) {
|
|
1834
2144
|
throw new FilloError3(`This form could not be rendered: ${normalized.error}`, 422);
|
|
1835
2145
|
}
|
|
1836
2146
|
const schema = normalized.schema;
|
|
1837
|
-
const syncedFormId = useCodeFormSync(form, client);
|
|
2147
|
+
const { formId: syncedFormId } = useCodeFormSync(form, client);
|
|
1838
2148
|
const api = useFilloController({
|
|
1839
2149
|
...options,
|
|
1840
2150
|
client,
|
|
1841
2151
|
form: schema,
|
|
1842
2152
|
formId: syncedFormId ?? formId,
|
|
1843
|
-
// Headless
|
|
1844
|
-
//
|
|
2153
|
+
// Headless = your markup, no Fillo-rendered layout. Free like every embed
|
|
2154
|
+
// method; recorded per response so usage stays measurable.
|
|
1845
2155
|
surface: "headless"
|
|
1846
2156
|
});
|
|
1847
|
-
return /* @__PURE__ */
|
|
2157
|
+
return /* @__PURE__ */ jsx7(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx7(FilloStringsContext.Provider, { value: resolveStrings2(strings), children: /* @__PURE__ */ jsx7(FilloAppearanceContext.Provider, { value: appearance, children: /* @__PURE__ */ jsx7(FilloInstanceIdContext.Provider, { value: instanceId, children }) }) }) });
|
|
1848
2158
|
}
|
|
1849
2159
|
|
|
1850
2160
|
// src/index.ts
|
|
1851
2161
|
import {
|
|
1852
2162
|
createClient as createClient2,
|
|
1853
2163
|
provisionWorkspace,
|
|
2164
|
+
when,
|
|
2165
|
+
FilloJsxError as FilloJsxError2,
|
|
1854
2166
|
FilloClient,
|
|
1855
2167
|
FilloError as FilloError4
|
|
1856
2168
|
} from "@usefillo/core";
|
|
2169
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
2170
|
+
const key = /* @__PURE__ */ Symbol.for("usefillo.react.instance");
|
|
2171
|
+
const g = globalThis;
|
|
2172
|
+
if (g[key]) {
|
|
2173
|
+
console.error(
|
|
2174
|
+
"[fillo] Two copies of @usefillo/react are loaded \u2014 React context does not cross package copies, so forms will misbehave. Dedupe the dependency (npm dedupe / pnpm why @usefillo/react)."
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
g[key] = true;
|
|
2178
|
+
}
|
|
1857
2179
|
export {
|
|
1858
2180
|
BlockRenderer,
|
|
2181
|
+
Fillo,
|
|
1859
2182
|
FilloClient,
|
|
1860
2183
|
FilloError4 as FilloError,
|
|
1861
2184
|
FileUploadField as FilloFileUpload,
|
|
1862
2185
|
FilloForm,
|
|
2186
|
+
FilloJsxError2 as FilloJsxError,
|
|
1863
2187
|
FilloProvider,
|
|
1864
2188
|
FormField,
|
|
1865
2189
|
createClient2 as createClient,
|
|
@@ -1867,5 +2191,7 @@ export {
|
|
|
1867
2191
|
provisionWorkspace,
|
|
1868
2192
|
useField,
|
|
1869
2193
|
useFillo,
|
|
1870
|
-
|
|
2194
|
+
useFilloAppearance,
|
|
2195
|
+
useFilloController,
|
|
2196
|
+
when
|
|
1871
2197
|
};
|