@usefillo/react 0.5.4 → 0.6.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/dist/index.d.ts +106 -6
- package/dist/index.js +469 -144
- 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([]);
|
|
@@ -662,16 +731,18 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
662
731
|
setValue(files.filter((f) => f.fileId !== fileId));
|
|
663
732
|
}
|
|
664
733
|
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
|
|
734
|
+
return /* @__PURE__ */ jsxs3("div", { ...slots.wrapperProps(`fillo-field fillo-field--file_upload${error ? " fillo-field--error" : ""}`), children: [
|
|
735
|
+
/* @__PURE__ */ jsxs3("label", { className: slots.label, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
|
|
667
736
|
field.label,
|
|
668
|
-
!field.required && /* @__PURE__ */ jsx3("span", { className: "fillo-optional", children:
|
|
737
|
+
!field.required && /* @__PURE__ */ jsx3("span", { className: "fillo-optional", children: strings.optional })
|
|
669
738
|
] }),
|
|
670
|
-
field.description && /* @__PURE__ */ jsx3("p", { className: "fillo
|
|
739
|
+
field.description && /* @__PURE__ */ jsx3("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
671
740
|
remaining > 0 && /* @__PURE__ */ jsxs3(
|
|
672
741
|
"div",
|
|
673
742
|
{
|
|
674
|
-
|
|
743
|
+
"data-fillo": "control",
|
|
744
|
+
"data-drag-over": dragOver ? "" : void 0,
|
|
745
|
+
className: slots.control(`fillo-dropzone${dragOver ? " fillo-dropzone--over" : ""}${canUpload ? "" : " fillo-dropzone--disabled"}`),
|
|
675
746
|
onDragOver: (e) => {
|
|
676
747
|
e.preventDefault();
|
|
677
748
|
setDragOver(true);
|
|
@@ -720,7 +791,16 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
720
791
|
]
|
|
721
792
|
}
|
|
722
793
|
),
|
|
723
|
-
/* @__PURE__ */ jsx3(
|
|
794
|
+
/* @__PURE__ */ jsx3(
|
|
795
|
+
"div",
|
|
796
|
+
{
|
|
797
|
+
role: "status",
|
|
798
|
+
"aria-live": "polite",
|
|
799
|
+
className: "fillo-sr-only",
|
|
800
|
+
style: { position: "absolute", left: "-9999px", width: 1, height: 1, overflow: "hidden" },
|
|
801
|
+
children: uploadStatus(inFlight, files.length)
|
|
802
|
+
}
|
|
803
|
+
),
|
|
724
804
|
(inFlight.length > 0 || files.length > 0) && /* @__PURE__ */ jsxs3("ul", { className: "fillo-files", children: [
|
|
725
805
|
files.map((f) => /* @__PURE__ */ jsxs3("li", { className: "fillo-file fillo-file--done", children: [
|
|
726
806
|
/* @__PURE__ */ jsx3("span", { className: "fillo-file-name", children: f.name }),
|
|
@@ -784,7 +864,7 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
784
864
|
f.key
|
|
785
865
|
))
|
|
786
866
|
] }),
|
|
787
|
-
error && /* @__PURE__ */ jsx3("p", { className: "fillo
|
|
867
|
+
error && /* @__PURE__ */ jsx3("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
788
868
|
] });
|
|
789
869
|
}
|
|
790
870
|
|
|
@@ -801,6 +881,18 @@ function fieldAria(field, error, ids) {
|
|
|
801
881
|
"aria-describedby": describedBy.length ? describedBy.join(" ") : void 0
|
|
802
882
|
};
|
|
803
883
|
}
|
|
884
|
+
function useControl(base, field, error) {
|
|
885
|
+
return {
|
|
886
|
+
className: useSlotClass(base, {
|
|
887
|
+
slot: "control",
|
|
888
|
+
kind: field.kind,
|
|
889
|
+
fieldId: field.id,
|
|
890
|
+
invalid: Boolean(error),
|
|
891
|
+
required: Boolean(field.required)
|
|
892
|
+
}),
|
|
893
|
+
"data-fillo": "control"
|
|
894
|
+
};
|
|
895
|
+
}
|
|
804
896
|
function isNativeControlTarget(target) {
|
|
805
897
|
return target instanceof HTMLElement && Boolean(target.closest("input, textarea, select, button"));
|
|
806
898
|
}
|
|
@@ -810,19 +902,37 @@ function FieldShell({
|
|
|
810
902
|
ids,
|
|
811
903
|
children
|
|
812
904
|
}) {
|
|
905
|
+
const state = {
|
|
906
|
+
kind: field.kind,
|
|
907
|
+
fieldId: field.id,
|
|
908
|
+
invalid: Boolean(error),
|
|
909
|
+
required: Boolean(field.required)
|
|
910
|
+
};
|
|
911
|
+
const fieldCls = useSlotClass(
|
|
912
|
+
`fillo-field fillo-field--${field.kind}${error ? " fillo-field--error" : ""}`,
|
|
913
|
+
{ slot: "field", ...state }
|
|
914
|
+
);
|
|
915
|
+
const labelCls = useSlotClass("fillo-label", { slot: "label", ...state });
|
|
916
|
+
const descriptionCls = useSlotClass("fillo-description", { slot: "fieldDescription", ...state });
|
|
917
|
+
const errorCls = useSlotClass("fillo-error", { slot: "error", ...state });
|
|
918
|
+
const strings = useStrings();
|
|
813
919
|
return /* @__PURE__ */ jsxs4(
|
|
814
920
|
"div",
|
|
815
921
|
{
|
|
816
|
-
className:
|
|
922
|
+
className: fieldCls,
|
|
923
|
+
"data-fillo": "field",
|
|
817
924
|
"data-field": field.id,
|
|
925
|
+
"data-kind": field.kind,
|
|
926
|
+
"data-invalid": error ? "" : void 0,
|
|
927
|
+
"data-required": field.required ? "" : void 0,
|
|
818
928
|
children: [
|
|
819
|
-
/* @__PURE__ */ jsxs4("label", { className: "fillo
|
|
929
|
+
/* @__PURE__ */ jsxs4("label", { className: labelCls, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
|
|
820
930
|
field.label,
|
|
821
|
-
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children:
|
|
931
|
+
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children: strings.optional })
|
|
822
932
|
] }),
|
|
823
|
-
field.description && /* @__PURE__ */ jsx4("p", { className: "fillo
|
|
933
|
+
field.description && /* @__PURE__ */ jsx4("p", { className: descriptionCls, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
824
934
|
children,
|
|
825
|
-
error && /* @__PURE__ */ jsx4("p", { className: "fillo
|
|
935
|
+
error && /* @__PURE__ */ jsx4("p", { className: errorCls, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
826
936
|
]
|
|
827
937
|
}
|
|
828
938
|
);
|
|
@@ -830,11 +940,12 @@ function FieldShell({
|
|
|
830
940
|
function TextInput({ field, value, error, setValue, ids: providedIds }) {
|
|
831
941
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
832
942
|
const type = field.kind === "email" ? "email" : field.kind === "url" ? "url" : field.kind === "number" ? "number" : field.kind === "date" ? "date" : "text";
|
|
943
|
+
const control = useControl("fillo-input", field, error);
|
|
833
944
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx4(
|
|
834
945
|
"input",
|
|
835
946
|
{
|
|
836
947
|
id: ids.inputId,
|
|
837
|
-
|
|
948
|
+
...control,
|
|
838
949
|
type,
|
|
839
950
|
value: typeof value === "string" || typeof value === "number" ? String(value) : "",
|
|
840
951
|
placeholder: field.placeholder,
|
|
@@ -845,11 +956,12 @@ function TextInput({ field, value, error, setValue, ids: providedIds }) {
|
|
|
845
956
|
}
|
|
846
957
|
function LongText({ field, value, error, setValue, ids: providedIds }) {
|
|
847
958
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
959
|
+
const control = useControl("fillo-input fillo-textarea", field, error);
|
|
848
960
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx4(
|
|
849
961
|
"textarea",
|
|
850
962
|
{
|
|
851
963
|
id: ids.inputId,
|
|
852
|
-
|
|
964
|
+
...control,
|
|
853
965
|
rows: 4,
|
|
854
966
|
value: typeof value === "string" ? value : "",
|
|
855
967
|
placeholder: field.placeholder,
|
|
@@ -898,6 +1010,9 @@ function ChoiceIcon({ icon }) {
|
|
|
898
1010
|
}
|
|
899
1011
|
function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
900
1012
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
1013
|
+
const appearance = useFilloAppearance();
|
|
1014
|
+
const strings = useStrings();
|
|
1015
|
+
const optionCls = (base, optionId, selected) => slotClass2(base, appearance, { slot: "option", kind: field.kind, fieldId: field.id, optionId, selected });
|
|
901
1016
|
const choice = field;
|
|
902
1017
|
const options = useDisplayOptions(choice);
|
|
903
1018
|
const isOtherValue = typeof value === "string" && value !== "" && !choice.options.some((o) => o.id === value);
|
|
@@ -906,7 +1021,8 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
906
1021
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs4(
|
|
907
1022
|
"div",
|
|
908
1023
|
{
|
|
909
|
-
className: "fillo-options",
|
|
1024
|
+
className: slotClass2("fillo-options", appearance, { slot: "options", kind: field.kind, fieldId: field.id }),
|
|
1025
|
+
"data-fillo": "options",
|
|
910
1026
|
role: "radiogroup",
|
|
911
1027
|
"aria-labelledby": ids.labelId,
|
|
912
1028
|
...fieldAria(field, error, ids),
|
|
@@ -915,7 +1031,13 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
915
1031
|
"label",
|
|
916
1032
|
{
|
|
917
1033
|
"data-option": opt.id,
|
|
918
|
-
|
|
1034
|
+
"data-fillo": "option",
|
|
1035
|
+
"data-selected": value === opt.id ? "" : void 0,
|
|
1036
|
+
className: optionCls(
|
|
1037
|
+
`fillo-option${opt.icon ? " fillo-option--has-icon" : ""}${value === opt.id ? " fillo-option--selected" : ""}`,
|
|
1038
|
+
opt.id,
|
|
1039
|
+
value === opt.id
|
|
1040
|
+
),
|
|
919
1041
|
onClick: (event) => {
|
|
920
1042
|
if (isNativeControlTarget(event.target)) return;
|
|
921
1043
|
event.preventDefault();
|
|
@@ -937,7 +1059,7 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
937
1059
|
}
|
|
938
1060
|
),
|
|
939
1061
|
/* @__PURE__ */ jsx4(ChoiceIcon, { icon: opt.icon }),
|
|
940
|
-
/* @__PURE__ */ jsx4("span", { className: "fillo-option-label", children: opt.label })
|
|
1062
|
+
/* @__PURE__ */ jsx4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
|
|
941
1063
|
]
|
|
942
1064
|
},
|
|
943
1065
|
opt.id
|
|
@@ -947,7 +1069,13 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
947
1069
|
/* @__PURE__ */ jsxs4(
|
|
948
1070
|
"div",
|
|
949
1071
|
{
|
|
950
|
-
|
|
1072
|
+
"data-fillo": "option",
|
|
1073
|
+
"data-selected": otherActive ? "" : void 0,
|
|
1074
|
+
className: optionCls(
|
|
1075
|
+
`fillo-option fillo-option--other${otherActive ? " fillo-option--selected fillo-option--with-other" : ""}`,
|
|
1076
|
+
"__other",
|
|
1077
|
+
otherActive
|
|
1078
|
+
),
|
|
951
1079
|
children: [
|
|
952
1080
|
/* @__PURE__ */ jsxs4("label", { className: "fillo-option-main", children: [
|
|
953
1081
|
/* @__PURE__ */ jsx4(
|
|
@@ -963,15 +1091,15 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
963
1091
|
}
|
|
964
1092
|
}
|
|
965
1093
|
),
|
|
966
|
-
/* @__PURE__ */ jsx4("span", { className: "fillo-option-label",
|
|
1094
|
+
/* @__PURE__ */ jsx4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
|
|
967
1095
|
] }),
|
|
968
1096
|
otherActive && /* @__PURE__ */ jsx4(
|
|
969
1097
|
"input",
|
|
970
1098
|
{
|
|
971
1099
|
type: "text",
|
|
972
1100
|
className: "fillo-input fillo-other-input",
|
|
973
|
-
"aria-label":
|
|
974
|
-
placeholder:
|
|
1101
|
+
"aria-label": strings.otherPrompt,
|
|
1102
|
+
placeholder: strings.otherPlaceholder,
|
|
975
1103
|
autoFocus: !isOtherValue,
|
|
976
1104
|
value: isOtherValue ? String(value) : "",
|
|
977
1105
|
onChange: (e) => setValue(e.target.value)
|
|
@@ -988,16 +1116,20 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
988
1116
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
989
1117
|
const choice = field;
|
|
990
1118
|
const options = useDisplayOptions(choice);
|
|
1119
|
+
const strings = useStrings();
|
|
991
1120
|
const optionIds = new Set(choice.options.map((o) => o.id));
|
|
992
1121
|
const selected = Array.isArray(value) ? value : [];
|
|
993
1122
|
const otherText = selected.find((v) => !optionIds.has(v));
|
|
994
1123
|
const [otherOn, setOtherOn] = useState5(otherText !== void 0);
|
|
995
1124
|
const otherActive = otherText !== void 0 || otherOn;
|
|
996
1125
|
const toggle = (id) => setValue(selected.includes(id) ? selected.filter((v) => v !== id) : [...selected, id]);
|
|
1126
|
+
const appearance = useFilloAppearance();
|
|
1127
|
+
const optionCls = (base, optionId, isSelected) => slotClass2(base, appearance, { slot: "option", kind: field.kind, fieldId: field.id, optionId, selected: isSelected });
|
|
997
1128
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs4(
|
|
998
1129
|
"div",
|
|
999
1130
|
{
|
|
1000
|
-
className: "fillo-options",
|
|
1131
|
+
className: slotClass2("fillo-options", appearance, { slot: "options", kind: field.kind, fieldId: field.id }),
|
|
1132
|
+
"data-fillo": "options",
|
|
1001
1133
|
role: "group",
|
|
1002
1134
|
"aria-labelledby": ids.labelId,
|
|
1003
1135
|
...fieldAria(field, error, ids),
|
|
@@ -1006,7 +1138,13 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1006
1138
|
"label",
|
|
1007
1139
|
{
|
|
1008
1140
|
"data-option": opt.id,
|
|
1009
|
-
|
|
1141
|
+
"data-fillo": "option",
|
|
1142
|
+
"data-selected": selected.includes(opt.id) ? "" : void 0,
|
|
1143
|
+
className: optionCls(
|
|
1144
|
+
`fillo-option${opt.icon ? " fillo-option--has-icon" : ""}${selected.includes(opt.id) ? " fillo-option--selected" : ""}`,
|
|
1145
|
+
opt.id,
|
|
1146
|
+
selected.includes(opt.id)
|
|
1147
|
+
),
|
|
1010
1148
|
onClick: (event) => {
|
|
1011
1149
|
if (isNativeControlTarget(event.target)) return;
|
|
1012
1150
|
event.preventDefault();
|
|
@@ -1023,7 +1161,7 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1023
1161
|
}
|
|
1024
1162
|
),
|
|
1025
1163
|
/* @__PURE__ */ jsx4(ChoiceIcon, { icon: opt.icon }),
|
|
1026
|
-
/* @__PURE__ */ jsx4("span", { className: "fillo-option-label", children: opt.label })
|
|
1164
|
+
/* @__PURE__ */ jsx4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
|
|
1027
1165
|
]
|
|
1028
1166
|
},
|
|
1029
1167
|
opt.id
|
|
@@ -1033,7 +1171,13 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1033
1171
|
/* @__PURE__ */ jsxs4(
|
|
1034
1172
|
"div",
|
|
1035
1173
|
{
|
|
1036
|
-
|
|
1174
|
+
"data-fillo": "option",
|
|
1175
|
+
"data-selected": otherActive ? "" : void 0,
|
|
1176
|
+
className: optionCls(
|
|
1177
|
+
`fillo-option fillo-option--other${otherActive ? " fillo-option--selected fillo-option--with-other" : ""}`,
|
|
1178
|
+
"__other",
|
|
1179
|
+
otherActive
|
|
1180
|
+
),
|
|
1037
1181
|
children: [
|
|
1038
1182
|
/* @__PURE__ */ jsxs4("label", { className: "fillo-option-main", children: [
|
|
1039
1183
|
/* @__PURE__ */ jsx4(
|
|
@@ -1052,15 +1196,15 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1052
1196
|
}
|
|
1053
1197
|
}
|
|
1054
1198
|
),
|
|
1055
|
-
/* @__PURE__ */ jsx4("span", { className: "fillo-option-label",
|
|
1199
|
+
/* @__PURE__ */ jsx4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
|
|
1056
1200
|
] }),
|
|
1057
1201
|
otherActive && /* @__PURE__ */ jsx4(
|
|
1058
1202
|
"input",
|
|
1059
1203
|
{
|
|
1060
1204
|
type: "text",
|
|
1061
1205
|
className: "fillo-input fillo-other-input",
|
|
1062
|
-
"aria-label":
|
|
1063
|
-
placeholder:
|
|
1206
|
+
"aria-label": strings.otherPrompt,
|
|
1207
|
+
placeholder: strings.otherPlaceholder,
|
|
1064
1208
|
autoFocus: otherText === void 0,
|
|
1065
1209
|
value: otherText ?? "",
|
|
1066
1210
|
onChange: (e) => {
|
|
@@ -1079,18 +1223,20 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1079
1223
|
var OTHER_SENTINEL = "__fw_other__";
|
|
1080
1224
|
function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
1081
1225
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
1226
|
+
const strings = useStrings();
|
|
1082
1227
|
const choice = field;
|
|
1083
1228
|
const options = useDisplayOptions(choice);
|
|
1084
1229
|
const isOtherValue = typeof value === "string" && value !== "" && !choice.options.some((o) => o.id === value);
|
|
1085
1230
|
const [otherOn, setOtherOn] = useState5(isOtherValue);
|
|
1086
1231
|
const otherActive = isOtherValue || value === "" && otherOn;
|
|
1232
|
+
const control = useControl("fillo-input fillo-select", field, error);
|
|
1087
1233
|
return /* @__PURE__ */ jsxs4(FieldShell, { field, error, ids, children: [
|
|
1088
1234
|
/* @__PURE__ */ jsxs4("div", { className: "fillo-select-wrap", children: [
|
|
1089
1235
|
/* @__PURE__ */ jsxs4(
|
|
1090
1236
|
"select",
|
|
1091
1237
|
{
|
|
1092
1238
|
id: ids.inputId,
|
|
1093
|
-
|
|
1239
|
+
...control,
|
|
1094
1240
|
...fieldAria(field, error, ids),
|
|
1095
1241
|
value: otherActive ? OTHER_SENTINEL : typeof value === "string" ? value : "",
|
|
1096
1242
|
onChange: (e) => {
|
|
@@ -1103,9 +1249,9 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1103
1249
|
}
|
|
1104
1250
|
},
|
|
1105
1251
|
children: [
|
|
1106
|
-
/* @__PURE__ */ jsx4("option", { value: "", children: field.placeholder ??
|
|
1252
|
+
/* @__PURE__ */ jsx4("option", { value: "", children: field.placeholder ?? strings.choosePlaceholder }),
|
|
1107
1253
|
options.map((opt) => /* @__PURE__ */ jsx4("option", { value: opt.id, children: opt.label }, opt.id)),
|
|
1108
|
-
choice.allowOther && /* @__PURE__ */ jsx4("option", { value: OTHER_SENTINEL, children:
|
|
1254
|
+
choice.allowOther && /* @__PURE__ */ jsx4("option", { value: OTHER_SENTINEL, children: `${strings.other}\u2026` })
|
|
1109
1255
|
]
|
|
1110
1256
|
}
|
|
1111
1257
|
),
|
|
@@ -1116,8 +1262,8 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1116
1262
|
{
|
|
1117
1263
|
type: "text",
|
|
1118
1264
|
className: "fillo-input fillo-other-input fillo-other-input--block",
|
|
1119
|
-
"aria-label":
|
|
1120
|
-
placeholder:
|
|
1265
|
+
"aria-label": strings.otherPrompt,
|
|
1266
|
+
placeholder: strings.otherPlaceholder,
|
|
1121
1267
|
autoFocus: !isOtherValue,
|
|
1122
1268
|
value: isOtherValue ? String(value) : "",
|
|
1123
1269
|
onChange: (e) => setValue(e.target.value)
|
|
@@ -1127,18 +1273,24 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1127
1273
|
}
|
|
1128
1274
|
function Checkbox({ field, value, error, setValue, ids: providedIds }) {
|
|
1129
1275
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
1276
|
+
const strings = useStrings();
|
|
1277
|
+
const appearance = useFilloAppearance();
|
|
1130
1278
|
const checkbox = field;
|
|
1131
1279
|
if (checkbox.appearance === "toggle") {
|
|
1132
1280
|
return /* @__PURE__ */ jsxs4(
|
|
1133
1281
|
"div",
|
|
1134
1282
|
{
|
|
1135
1283
|
className: `fillo-field fillo-field--checkbox fillo-field--toggle${error ? " fillo-field--error" : ""}`,
|
|
1284
|
+
"data-fillo": "field",
|
|
1136
1285
|
"data-field": field.id,
|
|
1286
|
+
"data-kind": field.kind,
|
|
1287
|
+
"data-invalid": error ? "" : void 0,
|
|
1288
|
+
"data-required": field.required ? "" : void 0,
|
|
1137
1289
|
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: [
|
|
1290
|
+
/* @__PURE__ */ jsxs4("label", { className: "fillo-toggle", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
|
|
1291
|
+
/* @__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
1292
|
field.label,
|
|
1141
|
-
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children:
|
|
1293
|
+
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children: strings.optional })
|
|
1142
1294
|
] }) }),
|
|
1143
1295
|
/* @__PURE__ */ jsx4(
|
|
1144
1296
|
"input",
|
|
@@ -1165,9 +1317,13 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1165
1317
|
"div",
|
|
1166
1318
|
{
|
|
1167
1319
|
className: `fillo-field fillo-field--checkbox${error ? " fillo-field--error" : ""}`,
|
|
1320
|
+
"data-fillo": "field",
|
|
1168
1321
|
"data-field": field.id,
|
|
1322
|
+
"data-kind": field.kind,
|
|
1323
|
+
"data-invalid": error ? "" : void 0,
|
|
1324
|
+
"data-required": field.required ? "" : void 0,
|
|
1169
1325
|
children: [
|
|
1170
|
-
/* @__PURE__ */ jsxs4("label", { className: "fillo-option", children: [
|
|
1326
|
+
/* @__PURE__ */ jsxs4("label", { className: "fillo-option", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
|
|
1171
1327
|
/* @__PURE__ */ jsx4(
|
|
1172
1328
|
"input",
|
|
1173
1329
|
{
|
|
@@ -1179,9 +1335,9 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1179
1335
|
...fieldAria(field, error, ids)
|
|
1180
1336
|
}
|
|
1181
1337
|
),
|
|
1182
|
-
/* @__PURE__ */ jsxs4("span", { className: "fillo-option-label", children: [
|
|
1338
|
+
/* @__PURE__ */ jsxs4("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: [
|
|
1183
1339
|
field.label,
|
|
1184
|
-
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children:
|
|
1340
|
+
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children: strings.optional })
|
|
1185
1341
|
] })
|
|
1186
1342
|
] }),
|
|
1187
1343
|
field.description && /* @__PURE__ */ jsx4("p", { className: "fillo-description", id: ids.descriptionId, children: field.description }),
|
|
@@ -1222,10 +1378,12 @@ function Rating({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1222
1378
|
const steps = Array.from({ length: max }, (_, i) => i + 1);
|
|
1223
1379
|
const selected = current > 0 ? current : null;
|
|
1224
1380
|
const tabbable = rovingIndex(steps, selected);
|
|
1381
|
+
const appearance = useFilloAppearance();
|
|
1382
|
+
const control = useControl("fillo-rating", field, error);
|
|
1225
1383
|
return /* @__PURE__ */ jsx4(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx4(
|
|
1226
1384
|
"div",
|
|
1227
1385
|
{
|
|
1228
|
-
|
|
1386
|
+
...control,
|
|
1229
1387
|
role: "radiogroup",
|
|
1230
1388
|
"aria-labelledby": ids.labelId,
|
|
1231
1389
|
...fieldAria(field, error, ids),
|
|
@@ -1234,7 +1392,14 @@ function Rating({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1234
1392
|
"button",
|
|
1235
1393
|
{
|
|
1236
1394
|
type: "button",
|
|
1237
|
-
|
|
1395
|
+
"data-fillo": "option",
|
|
1396
|
+
"data-selected": n <= current ? "" : void 0,
|
|
1397
|
+
className: slotClass2(`fillo-star${n <= current ? " fillo-star--active" : ""}`, appearance, {
|
|
1398
|
+
slot: "option",
|
|
1399
|
+
kind: field.kind,
|
|
1400
|
+
fieldId: field.id,
|
|
1401
|
+
selected: n <= current
|
|
1402
|
+
}),
|
|
1238
1403
|
role: "radio",
|
|
1239
1404
|
"aria-label": `${n} of ${max}`,
|
|
1240
1405
|
"aria-checked": n === current,
|
|
@@ -1264,11 +1429,13 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1264
1429
|
field.description ? ids.descriptionId : null,
|
|
1265
1430
|
error ? ids.errorId : null
|
|
1266
1431
|
].filter(Boolean).join(" ") || void 0;
|
|
1432
|
+
const appearance = useFilloAppearance();
|
|
1433
|
+
const control = useControl("fillo-scale", field, error);
|
|
1267
1434
|
return /* @__PURE__ */ jsxs4(FieldShell, { field, error, ids, children: [
|
|
1268
1435
|
/* @__PURE__ */ jsx4(
|
|
1269
1436
|
"div",
|
|
1270
1437
|
{
|
|
1271
|
-
|
|
1438
|
+
...control,
|
|
1272
1439
|
role: "radiogroup",
|
|
1273
1440
|
"aria-labelledby": ids.labelId,
|
|
1274
1441
|
"aria-invalid": error ? true : void 0,
|
|
@@ -1279,7 +1446,14 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1279
1446
|
"button",
|
|
1280
1447
|
{
|
|
1281
1448
|
type: "button",
|
|
1282
|
-
|
|
1449
|
+
"data-fillo": "option",
|
|
1450
|
+
"data-selected": value === n ? "" : void 0,
|
|
1451
|
+
className: slotClass2(`fillo-scale-step${value === n ? " fillo-scale-step--active" : ""}`, appearance, {
|
|
1452
|
+
slot: "option",
|
|
1453
|
+
kind: field.kind,
|
|
1454
|
+
fieldId: field.id,
|
|
1455
|
+
selected: value === n
|
|
1456
|
+
}),
|
|
1283
1457
|
role: "radio",
|
|
1284
1458
|
"aria-label": String(n),
|
|
1285
1459
|
"aria-checked": value === n,
|
|
@@ -1414,30 +1588,6 @@ function ContentRenderer({ block }) {
|
|
|
1414
1588
|
return /* @__PURE__ */ jsx4("hr", { className: "fillo-divider" });
|
|
1415
1589
|
}
|
|
1416
1590
|
}
|
|
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
1591
|
function blockHasPiping(block) {
|
|
1442
1592
|
const has = (s) => typeof s === "string" && s.includes("{{");
|
|
1443
1593
|
switch (block.kind) {
|
|
@@ -1497,7 +1647,15 @@ function BlockRendererInner({ block, api, components, customComponents }) {
|
|
|
1497
1647
|
error: api.errors[block.id],
|
|
1498
1648
|
setValue: (v) => {
|
|
1499
1649
|
api.setValue(block.id, v);
|
|
1500
|
-
|
|
1650
|
+
const ctx = {
|
|
1651
|
+
form: api.form,
|
|
1652
|
+
data: api.data,
|
|
1653
|
+
status: api.status,
|
|
1654
|
+
isLastPage: api.isLastPage,
|
|
1655
|
+
uploading: api.uploading
|
|
1656
|
+
};
|
|
1657
|
+
if (shouldAutoSubmit(block, v, ctx)) void api.submit().catch(() => {
|
|
1658
|
+
});
|
|
1501
1659
|
},
|
|
1502
1660
|
api,
|
|
1503
1661
|
ids
|
|
@@ -1511,8 +1669,13 @@ function FormField({
|
|
|
1511
1669
|
customComponents
|
|
1512
1670
|
}) {
|
|
1513
1671
|
const api = useFillo();
|
|
1672
|
+
const visibleIds = useMemo4(
|
|
1673
|
+
() => new Set(visibleFields(api.form, api.data).map((f) => f.id)),
|
|
1674
|
+
[api.form, api.data]
|
|
1675
|
+
);
|
|
1514
1676
|
const block = api.form.pages.flatMap((p) => p.blocks).find((b) => b.id === id);
|
|
1515
1677
|
if (!block) return null;
|
|
1678
|
+
if (isField(block) && !visibleIds.has(block.id)) return null;
|
|
1516
1679
|
return /* @__PURE__ */ jsx4(
|
|
1517
1680
|
BlockRenderer,
|
|
1518
1681
|
{
|
|
@@ -1566,18 +1729,25 @@ function focusFirstInvalid(formEl) {
|
|
|
1566
1729
|
if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
|
|
1567
1730
|
else setTimeout(run, 0);
|
|
1568
1731
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1732
|
+
var HP_STYLE = {
|
|
1733
|
+
position: "absolute",
|
|
1734
|
+
left: "-9999px",
|
|
1735
|
+
top: "auto",
|
|
1736
|
+
width: "1px",
|
|
1737
|
+
height: "1px",
|
|
1738
|
+
opacity: 0,
|
|
1739
|
+
overflow: "hidden",
|
|
1740
|
+
pointerEvents: "none"
|
|
1741
|
+
};
|
|
1742
|
+
function assertHpHidden(el) {
|
|
1743
|
+
if (!el) return;
|
|
1744
|
+
for (const [prop, value] of Object.entries(HP_STYLE)) {
|
|
1745
|
+
el.style[prop] = String(value);
|
|
1746
|
+
}
|
|
1577
1747
|
}
|
|
1578
1748
|
function FilloForm(props) {
|
|
1579
1749
|
const { form, formId } = props;
|
|
1580
|
-
const client =
|
|
1750
|
+
const client = useMemo5(
|
|
1581
1751
|
() => props.client ?? (formId ? createClient() : void 0),
|
|
1582
1752
|
[props.client, formId]
|
|
1583
1753
|
);
|
|
@@ -1589,7 +1759,7 @@ function FilloForm(props) {
|
|
|
1589
1759
|
const codeForm = isCodeForm2(form) ? form : null;
|
|
1590
1760
|
const inlineSchema = codeForm ? codeForm.schema : form;
|
|
1591
1761
|
const onError = props.onError;
|
|
1592
|
-
const syncedFormId = useCodeFormSync(form, client, onError);
|
|
1762
|
+
const { formId: syncedFormId, status: syncedStatus } = useCodeFormSync(form, client, onError);
|
|
1593
1763
|
const onErrorRef = useRef5(onError);
|
|
1594
1764
|
useEffect7(() => {
|
|
1595
1765
|
onErrorRef.current = onError;
|
|
@@ -1616,24 +1786,31 @@ function FilloForm(props) {
|
|
|
1616
1786
|
cancelled = true;
|
|
1617
1787
|
};
|
|
1618
1788
|
}, [needsFetch, client, formId]);
|
|
1789
|
+
const strings = resolveStrings(props.strings);
|
|
1619
1790
|
const rawSchema = inlineSchema ?? fetched;
|
|
1620
|
-
const normalized =
|
|
1791
|
+
const normalized = useMemo5(() => rawSchema ? normalizeFormSchema(rawSchema) : null, [rawSchema]);
|
|
1621
1792
|
const schema = normalized?.ok ? normalized.schema : null;
|
|
1622
|
-
const theme = props.theme ?? codeForm?.theme ?? fetchedTheme ?? void 0;
|
|
1793
|
+
const theme = props.appearance?.theme ?? props.theme ?? codeForm?.theme ?? fetchedTheme ?? void 0;
|
|
1623
1794
|
if (normalized && !normalized.ok) {
|
|
1624
1795
|
const error = new FilloError2(`This form could not be rendered: ${normalized.error}`, 422);
|
|
1625
1796
|
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:
|
|
1797
|
+
return /* @__PURE__ */ jsx5("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: strings.renderFailed });
|
|
1627
1798
|
}
|
|
1628
1799
|
if (loadError) {
|
|
1629
1800
|
if (props.renderError) {
|
|
1630
1801
|
return /* @__PURE__ */ jsx5(Fragment2, { children: props.renderError(loadError) });
|
|
1631
1802
|
}
|
|
1632
|
-
const message = loadError.status === 404 ?
|
|
1803
|
+
const message = loadError.status === 404 ? strings.loadFailedNotFound : loadError.status === 0 ? strings.loadFailedNetwork : strings.loadFailed;
|
|
1633
1804
|
return /* @__PURE__ */ jsx5("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: message });
|
|
1634
1805
|
}
|
|
1635
1806
|
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:
|
|
1807
|
+
return /* @__PURE__ */ jsx5("div", { className: `fillo-form fillo-form--closed ${props.className ?? ""}`, ...themeProps(theme), children: /* @__PURE__ */ jsx5("p", { className: "fillo-closed", children: strings.closed }) });
|
|
1808
|
+
}
|
|
1809
|
+
const notPublished = Boolean(codeForm) && syncedStatus === "draft" && !props.skipValidation;
|
|
1810
|
+
if (notPublished && !isDevEnv()) {
|
|
1811
|
+
const error = new FilloError2("This form isn't published yet.", 403);
|
|
1812
|
+
if (props.renderError) return /* @__PURE__ */ jsx5(Fragment2, { children: props.renderError(error) });
|
|
1813
|
+
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
1814
|
}
|
|
1638
1815
|
if (!schema) {
|
|
1639
1816
|
return /* @__PURE__ */ jsxs5(
|
|
@@ -1661,7 +1838,8 @@ function FilloForm(props) {
|
|
|
1661
1838
|
formId: syncedFormId ?? formId,
|
|
1662
1839
|
poweredBy,
|
|
1663
1840
|
brandLabel: fetchedBranding?.label,
|
|
1664
|
-
brandHref: fetchedBranding?.href
|
|
1841
|
+
brandHref: fetchedBranding?.href,
|
|
1842
|
+
draftNotice: notPublished
|
|
1665
1843
|
}
|
|
1666
1844
|
);
|
|
1667
1845
|
}
|
|
@@ -1679,10 +1857,25 @@ function DevNoClientNotice() {
|
|
|
1679
1857
|
" to collect them in Fillo, or forward them with webhooks. (This notice only appears in development.)"
|
|
1680
1858
|
] });
|
|
1681
1859
|
}
|
|
1860
|
+
function DevDraftNotice() {
|
|
1861
|
+
return /* @__PURE__ */ jsxs5("div", { className: "fillo-devwarning", role: "alert", children: [
|
|
1862
|
+
/* @__PURE__ */ jsx5("strong", { children: "Draft form." }),
|
|
1863
|
+
" It renders here, but it won\u2019t accept responses until you publish it in your Fillo dashboard. (This notice only appears in development.)"
|
|
1864
|
+
] });
|
|
1865
|
+
}
|
|
1682
1866
|
function ResolvedForm(props) {
|
|
1683
1867
|
const { schema, theme } = props;
|
|
1684
1868
|
const instanceId = useId2().replace(/:/g, "");
|
|
1685
1869
|
const hpRef = useRef5("");
|
|
1870
|
+
const codeForm = isCodeForm2(props.form) ? props.form : null;
|
|
1871
|
+
const client = props.client;
|
|
1872
|
+
const resolveFormId = useMemo5(
|
|
1873
|
+
() => codeForm && client?.key ? (
|
|
1874
|
+
// bypassCache: a stale cached formId must not be trusted at submit time.
|
|
1875
|
+
() => syncCodeForm2(client, codeForm, { bypassCache: true }).then((r) => r.formId)
|
|
1876
|
+
) : void 0,
|
|
1877
|
+
[codeForm, client]
|
|
1878
|
+
);
|
|
1686
1879
|
const api = useFilloController({
|
|
1687
1880
|
form: schema,
|
|
1688
1881
|
formId: props.formId,
|
|
@@ -1691,9 +1884,13 @@ function ResolvedForm(props) {
|
|
|
1691
1884
|
onChange: props.onChange,
|
|
1692
1885
|
onSubmitted: props.onSubmitted,
|
|
1693
1886
|
skipValidation: props.skipValidation,
|
|
1694
|
-
getHoneypot: () => hpRef.current
|
|
1887
|
+
getHoneypot: () => hpRef.current,
|
|
1888
|
+
// Framed renderer: Fillo draws the layout (and badge), so the surface is
|
|
1889
|
+
// "default", never the bare-engine "headless".
|
|
1890
|
+
surface: "default",
|
|
1891
|
+
resolveFormId
|
|
1695
1892
|
});
|
|
1696
|
-
const themed =
|
|
1893
|
+
const themed = useMemo5(() => themeProps(theme), [theme]);
|
|
1697
1894
|
const settings = schema.settings;
|
|
1698
1895
|
const submitted = api.status === "submitted";
|
|
1699
1896
|
useEffect7(() => {
|
|
@@ -1712,6 +1909,8 @@ function ResolvedForm(props) {
|
|
|
1712
1909
|
prevPageIndex.current = api.pageIndex;
|
|
1713
1910
|
blocksRef.current?.focus();
|
|
1714
1911
|
}, [api.pageIndex]);
|
|
1912
|
+
const appearance = props.appearance;
|
|
1913
|
+
const strings = resolveStrings(props.strings);
|
|
1715
1914
|
if (api.status === "submitted") {
|
|
1716
1915
|
return /* @__PURE__ */ jsxs5(
|
|
1717
1916
|
"div",
|
|
@@ -1720,14 +1919,27 @@ function ResolvedForm(props) {
|
|
|
1720
1919
|
tabIndex: -1,
|
|
1721
1920
|
role: "status",
|
|
1722
1921
|
"aria-live": "polite",
|
|
1723
|
-
className:
|
|
1922
|
+
className: slotClass3(
|
|
1923
|
+
`fillo-form fillo-form--success ${props.className ?? ""}`,
|
|
1924
|
+
appearance,
|
|
1925
|
+
{ slot: "root", status: "submitted" }
|
|
1926
|
+
),
|
|
1927
|
+
"data-fillo": "root",
|
|
1928
|
+
"data-state": "submitted",
|
|
1724
1929
|
...themed,
|
|
1725
1930
|
children: [
|
|
1726
|
-
props.renderSuccess ? props.renderSuccess() : /* @__PURE__ */ jsxs5(
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1931
|
+
props.renderSuccess ? props.renderSuccess() : /* @__PURE__ */ jsxs5(
|
|
1932
|
+
"div",
|
|
1933
|
+
{
|
|
1934
|
+
className: slotClass3("fillo-success", appearance, { slot: "success", status: "submitted" }),
|
|
1935
|
+
"data-fillo": "success",
|
|
1936
|
+
children: [
|
|
1937
|
+
/* @__PURE__ */ jsx5("div", { className: "fillo-success-mark", "aria-hidden": "true", children: "\u2713" }),
|
|
1938
|
+
/* @__PURE__ */ jsx5("h2", { className: "fillo-success-title", children: settings.successTitle ?? strings.successTitle }),
|
|
1939
|
+
/* @__PURE__ */ jsx5("p", { className: "fillo-success-message", children: settings.successMessage ?? strings.successMessage })
|
|
1940
|
+
]
|
|
1941
|
+
}
|
|
1942
|
+
),
|
|
1731
1943
|
props.poweredBy && /* @__PURE__ */ jsx5(PoweredBy, { label: props.brandLabel, href: props.brandHref })
|
|
1732
1944
|
]
|
|
1733
1945
|
}
|
|
@@ -1737,27 +1949,38 @@ function ResolvedForm(props) {
|
|
|
1737
1949
|
const autoSubmit = settings.submitMode === "auto";
|
|
1738
1950
|
const showPrimaryButton = !autoSubmit || !api.isLastPage || needsExplicitSubmit(visibleFields2(api.form, api.data));
|
|
1739
1951
|
const showFooter = showPrimaryButton || multiPage && !api.isFirstPage;
|
|
1740
|
-
return /* @__PURE__ */ jsx5(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx5(FilloInstanceIdContext.Provider, { value: instanceId, children: /* @__PURE__ */ jsxs5(
|
|
1952
|
+
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
1953
|
"form",
|
|
1742
1954
|
{
|
|
1743
|
-
className: `fillo-form ${props.className ?? ""}`,
|
|
1955
|
+
className: slotClass3(`fillo-form ${props.className ?? ""}`, appearance, {
|
|
1956
|
+
slot: "root",
|
|
1957
|
+
status: api.status
|
|
1958
|
+
}),
|
|
1959
|
+
"data-fillo": "root",
|
|
1960
|
+
"data-state": api.status,
|
|
1961
|
+
"data-page": api.pageIndex + 1,
|
|
1962
|
+
"data-last-page": api.isLastPage ? "" : void 0,
|
|
1744
1963
|
...themed,
|
|
1745
1964
|
noValidate: true,
|
|
1746
1965
|
onSubmit: (e) => {
|
|
1747
1966
|
e.preventDefault();
|
|
1748
1967
|
const formEl = e.currentTarget;
|
|
1749
|
-
if (api.isLastPage)
|
|
1750
|
-
|
|
1968
|
+
if (api.isLastPage) {
|
|
1969
|
+
void api.submit().then(() => focusFirstInvalid(formEl)).catch(() => {
|
|
1970
|
+
});
|
|
1971
|
+
} else {
|
|
1751
1972
|
api.next();
|
|
1752
1973
|
focusFirstInvalid(formEl);
|
|
1753
1974
|
}
|
|
1754
1975
|
},
|
|
1755
1976
|
children: [
|
|
1756
1977
|
!props.client && !props.skipValidation && isDevEnv() && /* @__PURE__ */ jsx5(DevNoClientNotice, {}),
|
|
1978
|
+
props.draftNotice && isDevEnv() && /* @__PURE__ */ jsx5(DevDraftNotice, {}),
|
|
1757
1979
|
multiPage && settings.showProgress !== false && /* @__PURE__ */ jsx5(
|
|
1758
1980
|
"div",
|
|
1759
1981
|
{
|
|
1760
|
-
className: "fillo-progress-track",
|
|
1982
|
+
className: slotClass3("fillo-progress-track", appearance, { slot: "progress" }),
|
|
1983
|
+
"data-fillo": "progress",
|
|
1761
1984
|
role: "progressbar",
|
|
1762
1985
|
"aria-valuemin": 0,
|
|
1763
1986
|
"aria-valuemax": api.pageCount,
|
|
@@ -1765,18 +1988,26 @@ function ResolvedForm(props) {
|
|
|
1765
1988
|
children: /* @__PURE__ */ jsx5(
|
|
1766
1989
|
"div",
|
|
1767
1990
|
{
|
|
1768
|
-
className: "fillo-progress-fill",
|
|
1769
|
-
|
|
1991
|
+
className: slotClass3("fillo-progress-fill", appearance, { slot: "progressFill" }),
|
|
1992
|
+
"data-fillo": "progressFill",
|
|
1993
|
+
style: { "--fillo-progress-value": `${(api.pageIndex + 1) / api.pageCount * 100}%` }
|
|
1770
1994
|
}
|
|
1771
1995
|
)
|
|
1772
1996
|
}
|
|
1773
1997
|
),
|
|
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(
|
|
1998
|
+
api.pageIndex === 0 && props.showTitle !== false && /* @__PURE__ */ jsxs5("header", { className: slotClass3("fillo-header", appearance, { slot: "header" }), "data-fillo": "header", children: [
|
|
1999
|
+
/* @__PURE__ */ jsx5("h1", { className: slotClass3("fillo-title", appearance, { slot: "title" }), "data-fillo": "title", children: schema.title }),
|
|
2000
|
+
schema.description && /* @__PURE__ */ jsx5(
|
|
2001
|
+
"p",
|
|
2002
|
+
{
|
|
2003
|
+
className: slotClass3("fillo-form-description", appearance, { slot: "description" }),
|
|
2004
|
+
"data-fillo": "description",
|
|
2005
|
+
children: schema.description
|
|
2006
|
+
}
|
|
2007
|
+
)
|
|
1777
2008
|
] }),
|
|
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(
|
|
2009
|
+
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 }),
|
|
2010
|
+
/* @__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
2011
|
BlockRenderer,
|
|
1781
2012
|
{
|
|
1782
2013
|
block,
|
|
@@ -1796,45 +2027,123 @@ function ResolvedForm(props) {
|
|
|
1796
2027
|
autoComplete: "off",
|
|
1797
2028
|
"aria-hidden": "true",
|
|
1798
2029
|
defaultValue: "",
|
|
2030
|
+
style: HP_STYLE,
|
|
2031
|
+
ref: assertHpHidden,
|
|
1799
2032
|
onChange: (e) => {
|
|
1800
2033
|
hpRef.current = e.target.value;
|
|
1801
2034
|
}
|
|
1802
2035
|
}
|
|
1803
2036
|
),
|
|
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(
|
|
2037
|
+
(api.status === "error" || api.submitError) && /* @__PURE__ */ jsx5("p", { className: "fillo-submit-error", role: "alert", children: api.submitError ?? strings.submitFailed }),
|
|
2038
|
+
showFooter && /* @__PURE__ */ jsxs5("footer", { className: slotClass3("fillo-footer", appearance, { slot: "footer" }), "data-fillo": "footer", children: [
|
|
2039
|
+
multiPage && !api.isFirstPage && /* @__PURE__ */ jsx5(
|
|
2040
|
+
"button",
|
|
2041
|
+
{
|
|
2042
|
+
type: "button",
|
|
2043
|
+
className: slotClass3("fillo-button fillo-button--ghost", appearance, { slot: "button", variant: "ghost" }),
|
|
2044
|
+
"data-fillo": "button",
|
|
2045
|
+
onClick: api.back,
|
|
2046
|
+
children: strings.back
|
|
2047
|
+
}
|
|
2048
|
+
),
|
|
1807
2049
|
showPrimaryButton && /* @__PURE__ */ jsx5(
|
|
1808
2050
|
"button",
|
|
1809
2051
|
{
|
|
1810
2052
|
type: "submit",
|
|
1811
|
-
className: "fillo-button fillo-button--primary",
|
|
2053
|
+
className: slotClass3("fillo-button fillo-button--primary", appearance, { slot: "button", variant: "primary" }),
|
|
2054
|
+
"data-fillo": "button",
|
|
1812
2055
|
disabled: api.status === "submitting" || api.uploading,
|
|
1813
|
-
children: api.uploading ?
|
|
2056
|
+
children: api.uploading ? strings.uploading : api.status === "submitting" ? strings.submitting : api.isLastPage ? settings.submitLabel ?? strings.submit : strings.next
|
|
1814
2057
|
}
|
|
1815
2058
|
)
|
|
1816
2059
|
] }),
|
|
1817
2060
|
props.poweredBy && /* @__PURE__ */ jsx5(PoweredBy, { label: props.brandLabel, href: props.brandHref })
|
|
1818
2061
|
]
|
|
1819
2062
|
}
|
|
1820
|
-
) }) });
|
|
2063
|
+
) }) }) }) });
|
|
1821
2064
|
}
|
|
1822
2065
|
|
|
1823
|
-
// src/
|
|
1824
|
-
import {
|
|
1825
|
-
import {
|
|
2066
|
+
// src/jsx.tsx
|
|
2067
|
+
import { useRef as useRef6 } from "react";
|
|
2068
|
+
import {
|
|
2069
|
+
codeFormFromJsx,
|
|
2070
|
+
FilloJsxError,
|
|
2071
|
+
JSX_BLOCK_COMPONENTS
|
|
2072
|
+
} from "@usefillo/core";
|
|
1826
2073
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1827
|
-
|
|
2074
|
+
var B = JSX_BLOCK_COMPONENTS;
|
|
2075
|
+
var isDevEnv2 = () => typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
2076
|
+
function JsxForm(props) {
|
|
2077
|
+
const { id, title, description, settings, theme, client, children, ...rest } = props;
|
|
2078
|
+
const compiled = codeFormFromJsx({ id, title, description, settings, theme }, children);
|
|
2079
|
+
const json = JSON.stringify(compiled.schema) + JSON.stringify(compiled.theme ?? null);
|
|
2080
|
+
const memo2 = useRef6(null);
|
|
2081
|
+
if (memo2.current && memo2.current.json !== json && isDevEnv2()) {
|
|
2082
|
+
console.warn(
|
|
2083
|
+
`[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.`
|
|
2084
|
+
);
|
|
2085
|
+
}
|
|
2086
|
+
if (!memo2.current || memo2.current.json !== json) {
|
|
2087
|
+
memo2.current = { json, form: compiled };
|
|
2088
|
+
}
|
|
2089
|
+
return /* @__PURE__ */ jsx6(FilloForm, { form: memo2.current.form, client, ...rest });
|
|
2090
|
+
}
|
|
2091
|
+
function defineFormFromJsx(element) {
|
|
2092
|
+
if (!element || element.type !== JsxForm) {
|
|
2093
|
+
throw new FilloJsxError(
|
|
2094
|
+
"NON_FILLO_CHILD",
|
|
2095
|
+
"Fillo.defineForm() takes a <Fillo.Form> element: Fillo.defineForm(<Fillo.Form id=\u2026>\u2026</Fillo.Form>)"
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
const { id, title, description, settings, theme, children } = element.props;
|
|
2099
|
+
return codeFormFromJsx({ id, title, description, settings, theme }, children);
|
|
2100
|
+
}
|
|
2101
|
+
var Fillo = {
|
|
2102
|
+
Form: JsxForm,
|
|
2103
|
+
/** Compile a <Fillo.Form> element to a CodeForm at module scope — the same
|
|
2104
|
+
* value FilloProvider (headless) and the CLI consume. */
|
|
2105
|
+
defineForm: defineFormFromJsx,
|
|
2106
|
+
Text: B.Text,
|
|
2107
|
+
LongText: B.LongText,
|
|
2108
|
+
Email: B.Email,
|
|
2109
|
+
Url: B.Url,
|
|
2110
|
+
Phone: B.Phone,
|
|
2111
|
+
Number: B.Number,
|
|
2112
|
+
Select: B.Select,
|
|
2113
|
+
MultiSelect: B.MultiSelect,
|
|
2114
|
+
Dropdown: B.Dropdown,
|
|
2115
|
+
Checkbox: B.Checkbox,
|
|
2116
|
+
Rating: B.Rating,
|
|
2117
|
+
Scale: B.Scale,
|
|
2118
|
+
Ranking: B.Ranking,
|
|
2119
|
+
Matrix: B.Matrix,
|
|
2120
|
+
Signature: B.Signature,
|
|
2121
|
+
Date: B.Date,
|
|
2122
|
+
FileUpload: B.FileUpload,
|
|
2123
|
+
Hidden: B.Hidden,
|
|
2124
|
+
Custom: B.Custom,
|
|
2125
|
+
Heading: B.Heading,
|
|
2126
|
+
Paragraph: B.Paragraph,
|
|
2127
|
+
Divider: B.Divider,
|
|
2128
|
+
Page: B.Page,
|
|
2129
|
+
Option: B.Option
|
|
2130
|
+
};
|
|
2131
|
+
|
|
2132
|
+
// src/provider.tsx
|
|
2133
|
+
import { useId as useId3, useMemo as useMemo6 } from "react";
|
|
2134
|
+
import { FilloError as FilloError3, normalizeFormSchema as normalizeFormSchema2, resolveStrings as resolveStrings2 } from "@usefillo/core";
|
|
2135
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2136
|
+
function FilloProvider({ children, form, formId, client, appearance, strings, ...options }) {
|
|
1828
2137
|
const instanceId = useId3().replace(/:/g, "");
|
|
1829
2138
|
const codeForm = isCodeForm2(form) ? form : null;
|
|
1830
2139
|
const rawSchema = codeForm ? codeForm.schema : form;
|
|
1831
|
-
const schemaKey =
|
|
1832
|
-
const normalized =
|
|
2140
|
+
const schemaKey = useMemo6(() => JSON.stringify(rawSchema), [rawSchema]);
|
|
2141
|
+
const normalized = useMemo6(() => normalizeFormSchema2(rawSchema), [rawSchema, schemaKey]);
|
|
1833
2142
|
if (!normalized.ok) {
|
|
1834
2143
|
throw new FilloError3(`This form could not be rendered: ${normalized.error}`, 422);
|
|
1835
2144
|
}
|
|
1836
2145
|
const schema = normalized.schema;
|
|
1837
|
-
const syncedFormId = useCodeFormSync(form, client);
|
|
2146
|
+
const { formId: syncedFormId } = useCodeFormSync(form, client);
|
|
1838
2147
|
const api = useFilloController({
|
|
1839
2148
|
...options,
|
|
1840
2149
|
client,
|
|
@@ -1844,22 +2153,36 @@ function FilloProvider({ children, form, formId, client, ...options }) {
|
|
|
1844
2153
|
// a paid capability the server enforces when these responses are submitted.
|
|
1845
2154
|
surface: "headless"
|
|
1846
2155
|
});
|
|
1847
|
-
return /* @__PURE__ */
|
|
2156
|
+
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
2157
|
}
|
|
1849
2158
|
|
|
1850
2159
|
// src/index.ts
|
|
1851
2160
|
import {
|
|
1852
2161
|
createClient as createClient2,
|
|
1853
2162
|
provisionWorkspace,
|
|
2163
|
+
when,
|
|
2164
|
+
FilloJsxError as FilloJsxError2,
|
|
1854
2165
|
FilloClient,
|
|
1855
2166
|
FilloError as FilloError4
|
|
1856
2167
|
} from "@usefillo/core";
|
|
2168
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
2169
|
+
const key = /* @__PURE__ */ Symbol.for("usefillo.react.instance");
|
|
2170
|
+
const g = globalThis;
|
|
2171
|
+
if (g[key]) {
|
|
2172
|
+
console.error(
|
|
2173
|
+
"[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)."
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
g[key] = true;
|
|
2177
|
+
}
|
|
1857
2178
|
export {
|
|
1858
2179
|
BlockRenderer,
|
|
2180
|
+
Fillo,
|
|
1859
2181
|
FilloClient,
|
|
1860
2182
|
FilloError4 as FilloError,
|
|
1861
2183
|
FileUploadField as FilloFileUpload,
|
|
1862
2184
|
FilloForm,
|
|
2185
|
+
FilloJsxError2 as FilloJsxError,
|
|
1863
2186
|
FilloProvider,
|
|
1864
2187
|
FormField,
|
|
1865
2188
|
createClient2 as createClient,
|
|
@@ -1867,5 +2190,7 @@ export {
|
|
|
1867
2190
|
provisionWorkspace,
|
|
1868
2191
|
useField,
|
|
1869
2192
|
useFillo,
|
|
1870
|
-
|
|
2193
|
+
useFilloAppearance,
|
|
2194
|
+
useFilloController,
|
|
2195
|
+
when
|
|
1871
2196
|
};
|