@usefillo/react 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -18
- package/dist/index.d.ts +46 -2
- package/dist/index.js +848 -382
- package/dist/styles.css +73 -0
- package/dist/styles.unlayered.css +73 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/FilloForm.tsx
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect as useEffect8,
|
|
7
|
+
useId as useId2,
|
|
8
|
+
useMemo as useMemo5,
|
|
9
|
+
useRef as useRef7,
|
|
10
|
+
useState as useState6,
|
|
11
|
+
useSyncExternalStore as useSyncExternalStore2
|
|
12
|
+
} from "react";
|
|
5
13
|
import {
|
|
6
14
|
FilloError as FilloError2,
|
|
7
15
|
createClient,
|
|
8
16
|
formSchemasEqual,
|
|
17
|
+
isBuildTimeDevEnv as isBuildTimeDevEnv2,
|
|
9
18
|
isFilloError as isFilloError2,
|
|
19
|
+
isLikelyDevEnv,
|
|
10
20
|
needsExplicitSubmit,
|
|
11
21
|
normalizeFormSchema,
|
|
12
22
|
normalizeFormTheme,
|
|
13
|
-
|
|
23
|
+
reachableFields,
|
|
24
|
+
reachablePageSequence
|
|
14
25
|
} from "@usefillo/core";
|
|
15
26
|
import { resolveStrings, slotClass as slotClass3 } from "@usefillo/core";
|
|
16
27
|
|
|
@@ -60,11 +71,90 @@ function useStrings() {
|
|
|
60
71
|
return useContext(FilloStringsContext);
|
|
61
72
|
}
|
|
62
73
|
|
|
74
|
+
// src/chrome.tsx
|
|
75
|
+
import { isBuildTimeDevEnv } from "@usefillo/core";
|
|
76
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
77
|
+
function safeHttpUrl(href) {
|
|
78
|
+
try {
|
|
79
|
+
if (!href) return null;
|
|
80
|
+
const url = new URL(href);
|
|
81
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
82
|
+
return url.toString();
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
var warnedPreviewInProduction = false;
|
|
88
|
+
function warnPreviewInProduction() {
|
|
89
|
+
if (warnedPreviewInProduction || isBuildTimeDevEnv()) return;
|
|
90
|
+
warnedPreviewInProduction = true;
|
|
91
|
+
console.warn(
|
|
92
|
+
"[fillo] `preview` is enabled in a production build. Preview is cosmetic only \u2014 it shows developer chrome and never changes where submissions go or whether they are accepted \u2014 but remove it before respondents see this page."
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function PreviewBadge() {
|
|
96
|
+
return /* @__PURE__ */ jsx("span", { className: "fillo-preview-badge", "data-fillo": "preview-badge", children: "Preview" });
|
|
97
|
+
}
|
|
98
|
+
function DevNoClientNotice() {
|
|
99
|
+
return /* @__PURE__ */ jsxs("div", { className: "fillo-devwarning", role: "alert", children: [
|
|
100
|
+
/* @__PURE__ */ jsx("strong", { children: "No Fillo client connected." }),
|
|
101
|
+
" This form is render-only and won\u2019t save responses. Pass a ",
|
|
102
|
+
/* @__PURE__ */ jsx("code", { children: "client" }),
|
|
103
|
+
" to collect them in Fillo, or forward them with webhooks. (This notice only appears in development and preview.)"
|
|
104
|
+
] });
|
|
105
|
+
}
|
|
106
|
+
function DevDraftNotice({ warningUrl }) {
|
|
107
|
+
const safeUrl = safeHttpUrl(warningUrl);
|
|
108
|
+
return /* @__PURE__ */ jsxs("div", { className: "fillo-devwarning", role: "alert", children: [
|
|
109
|
+
/* @__PURE__ */ jsx("strong", { children: "Draft form." }),
|
|
110
|
+
" It renders here, but it won\u2019t accept responses until you publish it in your Fillo dashboard.",
|
|
111
|
+
" ",
|
|
112
|
+
safeUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
113
|
+
"Connect storage to publish:",
|
|
114
|
+
" ",
|
|
115
|
+
/* @__PURE__ */ jsx("a", { href: safeUrl, target: "_blank", rel: "noopener noreferrer", children: safeUrl }),
|
|
116
|
+
".",
|
|
117
|
+
" "
|
|
118
|
+
] }),
|
|
119
|
+
"(This notice only appears in development and preview.)"
|
|
120
|
+
] });
|
|
121
|
+
}
|
|
122
|
+
function DevStagedNotice() {
|
|
123
|
+
return /* @__PURE__ */ jsxs("div", { className: "fillo-devwarning", role: "alert", children: [
|
|
124
|
+
/* @__PURE__ */ jsx("strong", { children: "Code changes are staged." }),
|
|
125
|
+
" This local preview is not the live form yet. Publish the changes in Fillo before deploying them for respondents."
|
|
126
|
+
] });
|
|
127
|
+
}
|
|
128
|
+
function DevSyncErrorNotice({ error }) {
|
|
129
|
+
return /* @__PURE__ */ jsxs("div", { className: "fillo-devwarning", role: "alert", children: [
|
|
130
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
131
|
+
"Form sync needs attention",
|
|
132
|
+
error.code ? ` (${error.code})` : "",
|
|
133
|
+
"."
|
|
134
|
+
] }),
|
|
135
|
+
" ",
|
|
136
|
+
error.message,
|
|
137
|
+
" The safe form remains available for development."
|
|
138
|
+
] });
|
|
139
|
+
}
|
|
140
|
+
function DevChrome(props) {
|
|
141
|
+
const notice = props.devNotices === false ? null : props.syncError ? /* @__PURE__ */ jsx(DevSyncErrorNotice, { error: props.syncError }) : props.staged ? /* @__PURE__ */ jsx(DevStagedNotice, {}) : props.draft ? /* @__PURE__ */ jsx(DevDraftNotice, { warningUrl: props.warningUrl }) : props.noClient ? /* @__PURE__ */ jsx(DevNoClientNotice, {}) : null;
|
|
142
|
+
if (!props.preview && !notice) return null;
|
|
143
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
144
|
+
props.preview && /* @__PURE__ */ jsx(PreviewBadge, {}),
|
|
145
|
+
notice
|
|
146
|
+
] });
|
|
147
|
+
}
|
|
148
|
+
|
|
63
149
|
// src/context.ts
|
|
64
150
|
import { allFields } from "@usefillo/core";
|
|
65
151
|
import { createContext as createContext2, useContext as useContext2, useId, useMemo } from "react";
|
|
66
152
|
var FilloContext = createContext2(null);
|
|
67
153
|
var FilloInstanceIdContext = createContext2(null);
|
|
154
|
+
var FilloChromeContext = createContext2(null);
|
|
155
|
+
function useFilloChrome() {
|
|
156
|
+
return useContext2(FilloChromeContext);
|
|
157
|
+
}
|
|
68
158
|
function useFilloInstanceId() {
|
|
69
159
|
const instanceId = useContext2(FilloInstanceIdContext);
|
|
70
160
|
const fallbackId = useId().replace(/:/g, "");
|
|
@@ -127,7 +217,18 @@ function useFilloController(options) {
|
|
|
127
217
|
surface: options.surface ?? "headless",
|
|
128
218
|
// Wrapped so the engine (created once) always calls the latest resolver.
|
|
129
219
|
resolveFormId: options.resolveFormId ? () => optsRef.current.resolveFormId() : void 0,
|
|
130
|
-
|
|
220
|
+
// A getter, not a captured boolean: the dev-chrome gate upgrades right
|
|
221
|
+
// after hydration (build-time snapshot → hostname-aware check), but the
|
|
222
|
+
// engine is created once at mount and reads this at submit time.
|
|
223
|
+
get verboseResolutionErrors() {
|
|
224
|
+
return optsRef.current.verboseResolutionErrors;
|
|
225
|
+
},
|
|
226
|
+
respondent: options.respondent,
|
|
227
|
+
// Challenge wiring read through optsRef so the engine (created once) sees
|
|
228
|
+
// the latest token/reset each submit — the widget solves after mount.
|
|
229
|
+
challengeRequired: options.challengeRequired,
|
|
230
|
+
getChallengeToken: () => optsRef.current.getChallengeToken?.(),
|
|
231
|
+
onChallengeFailed: () => optsRef.current.onChallengeFailed?.()
|
|
131
232
|
});
|
|
132
233
|
}
|
|
133
234
|
const store = storeRef.current;
|
|
@@ -158,11 +259,11 @@ function useFilloController(options) {
|
|
|
158
259
|
[store, state, options.form, options.formId, options.client]
|
|
159
260
|
);
|
|
160
261
|
}
|
|
161
|
-
function useDraftFlush(api) {
|
|
162
|
-
const
|
|
262
|
+
function useDraftFlush(api, enabled = true) {
|
|
263
|
+
const active = enabled && Boolean(api.form.settings.saveProgress);
|
|
163
264
|
const flush = api.flushDraft;
|
|
164
265
|
useEffect(() => {
|
|
165
|
-
if (!
|
|
266
|
+
if (!active || typeof window === "undefined") return;
|
|
166
267
|
const onPageHide = () => flush();
|
|
167
268
|
const onVisibility = () => {
|
|
168
269
|
if (document.visibilityState === "hidden") flush();
|
|
@@ -173,7 +274,7 @@ function useDraftFlush(api) {
|
|
|
173
274
|
window.removeEventListener("pagehide", onPageHide);
|
|
174
275
|
document.removeEventListener("visibilitychange", onVisibility);
|
|
175
276
|
};
|
|
176
|
-
}, [
|
|
277
|
+
}, [active, flush]);
|
|
177
278
|
}
|
|
178
279
|
|
|
179
280
|
// src/define.ts
|
|
@@ -224,8 +325,14 @@ function useCodeFormSync(form, client, onError, options) {
|
|
|
224
325
|
error: null,
|
|
225
326
|
noticeError: null,
|
|
226
327
|
staged: false,
|
|
328
|
+
accepting: void 0,
|
|
329
|
+
acceptingReason: void 0,
|
|
330
|
+
uploadsAvailable: void 0,
|
|
227
331
|
resolvedSchema: void 0,
|
|
228
|
-
resolvedTheme: void 0
|
|
332
|
+
resolvedTheme: void 0,
|
|
333
|
+
warning: void 0,
|
|
334
|
+
warningCode: void 0,
|
|
335
|
+
warningUrl: void 0
|
|
229
336
|
} : {
|
|
230
337
|
key: syncKey,
|
|
231
338
|
client,
|
|
@@ -276,14 +383,25 @@ function useCodeFormSync(form, client, onError, options) {
|
|
|
276
383
|
formId: r.formId,
|
|
277
384
|
status: r.status ?? null,
|
|
278
385
|
branding: r.branding,
|
|
386
|
+
challenge: r.challenge,
|
|
279
387
|
error: null,
|
|
280
388
|
noticeError,
|
|
281
389
|
staged: Boolean(r.staged),
|
|
390
|
+
accepting: r.accepting,
|
|
391
|
+
acceptingReason: r.acceptingReason,
|
|
392
|
+
uploadsAvailable: r.uploadsAvailable,
|
|
282
393
|
resolvedSchema: r.resolvedSchema,
|
|
283
|
-
resolvedTheme: r.resolvedTheme
|
|
394
|
+
resolvedTheme: r.resolvedTheme,
|
|
395
|
+
warning: r.warning,
|
|
396
|
+
warningCode: r.warningCode,
|
|
397
|
+
warningUrl: r.warningUrl
|
|
284
398
|
});
|
|
285
399
|
const dashboardUrl = `${client.baseUrl || "https://fillo.so"}/forms/${r.formId}`;
|
|
286
|
-
if (r.warning)
|
|
400
|
+
if (r.warning) {
|
|
401
|
+
console.warn(
|
|
402
|
+
`[fillo] ${r.warning}${r.warningUrl ? ` Connect storage to publish: ${r.warningUrl}` : ""}`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
287
405
|
if (noticeError) {
|
|
288
406
|
console.warn(
|
|
289
407
|
`[fillo] form sync needs attention (${noticeError.code}): ${noticeError.message}`
|
|
@@ -325,12 +443,19 @@ function useCodeFormSync(form, client, onError, options) {
|
|
|
325
443
|
formId: synced.formId,
|
|
326
444
|
status: synced.status,
|
|
327
445
|
branding: synced.branding,
|
|
446
|
+
challenge: synced.challenge,
|
|
328
447
|
error: synced.error,
|
|
329
448
|
noticeError: synced.noticeError,
|
|
330
449
|
syncing: Boolean(syncKey) && !synced.settled,
|
|
331
450
|
staged: synced.staged,
|
|
451
|
+
accepting: synced.accepting,
|
|
452
|
+
acceptingReason: synced.acceptingReason,
|
|
453
|
+
uploadsAvailable: synced.uploadsAvailable,
|
|
332
454
|
resolvedSchema: synced.resolvedSchema,
|
|
333
|
-
resolvedTheme: synced.resolvedTheme
|
|
455
|
+
resolvedTheme: synced.resolvedTheme,
|
|
456
|
+
warning: synced.warning,
|
|
457
|
+
warningCode: synced.warningCode,
|
|
458
|
+
warningUrl: synced.warningUrl
|
|
334
459
|
} : {
|
|
335
460
|
formId: null,
|
|
336
461
|
status: null,
|
|
@@ -358,7 +483,7 @@ import {
|
|
|
358
483
|
toE164
|
|
359
484
|
} from "@usefillo/core";
|
|
360
485
|
import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState2 } from "react";
|
|
361
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
486
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
362
487
|
function localeCountry() {
|
|
363
488
|
if (typeof navigator === "undefined") return void 0;
|
|
364
489
|
const tag = navigator.languages?.[0] || navigator.language || "";
|
|
@@ -441,15 +566,15 @@ function PhoneField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
441
566
|
inputRef.current?.focus();
|
|
442
567
|
}
|
|
443
568
|
const describedBy = [field.description ? ids.descriptionId : null, error ? ids.errorId : null].filter(Boolean).join(" ") || void 0;
|
|
444
|
-
return /* @__PURE__ */
|
|
445
|
-
/* @__PURE__ */
|
|
569
|
+
return /* @__PURE__ */ jsxs2("div", { ...slots.wrapperProps(`fillo-field fillo-field--phone${error ? " fillo-field--error" : ""}`), children: [
|
|
570
|
+
/* @__PURE__ */ jsxs2("label", { className: slots.label, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
|
|
446
571
|
field.label,
|
|
447
|
-
!field.required && /* @__PURE__ */
|
|
572
|
+
!field.required && /* @__PURE__ */ jsx2("span", { className: "fillo-optional", children: strings.optional })
|
|
448
573
|
] }),
|
|
449
|
-
field.description && /* @__PURE__ */
|
|
450
|
-
/* @__PURE__ */
|
|
451
|
-
/* @__PURE__ */
|
|
452
|
-
/* @__PURE__ */
|
|
574
|
+
field.description && /* @__PURE__ */ jsx2("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
575
|
+
/* @__PURE__ */ jsxs2("div", { className: "fillo-phone", children: [
|
|
576
|
+
/* @__PURE__ */ jsx2(CountrySelect, { country, onPick: pickCountry, labelledBy: ids.labelId }),
|
|
577
|
+
/* @__PURE__ */ jsx2(
|
|
453
578
|
"input",
|
|
454
579
|
{
|
|
455
580
|
ref: inputRef,
|
|
@@ -468,7 +593,7 @@ function PhoneField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
468
593
|
}
|
|
469
594
|
)
|
|
470
595
|
] }),
|
|
471
|
-
error && /* @__PURE__ */
|
|
596
|
+
error && /* @__PURE__ */ jsx2("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
472
597
|
] });
|
|
473
598
|
}
|
|
474
599
|
function CountrySelect({
|
|
@@ -544,8 +669,8 @@ function CountrySelect({
|
|
|
544
669
|
triggerRef.current?.focus();
|
|
545
670
|
}
|
|
546
671
|
}
|
|
547
|
-
return /* @__PURE__ */
|
|
548
|
-
/* @__PURE__ */
|
|
672
|
+
return /* @__PURE__ */ jsxs2("div", { className: "fillo-phone-country", ref: rootRef, children: [
|
|
673
|
+
/* @__PURE__ */ jsxs2(
|
|
549
674
|
"button",
|
|
550
675
|
{
|
|
551
676
|
ref: triggerRef,
|
|
@@ -557,17 +682,17 @@ function CountrySelect({
|
|
|
557
682
|
"aria-label": `Country: ${country.name} (+${country.dialCode})`,
|
|
558
683
|
onClick: () => setOpen((o) => !o),
|
|
559
684
|
children: [
|
|
560
|
-
/* @__PURE__ */
|
|
561
|
-
/* @__PURE__ */
|
|
685
|
+
/* @__PURE__ */ jsx2("span", { className: "fillo-phone-flag-emoji", "aria-hidden": "true", children: flagEmoji(country.iso2) }),
|
|
686
|
+
/* @__PURE__ */ jsxs2("span", { className: "fillo-phone-dial", children: [
|
|
562
687
|
"+",
|
|
563
688
|
country.dialCode
|
|
564
689
|
] }),
|
|
565
|
-
/* @__PURE__ */
|
|
690
|
+
/* @__PURE__ */ jsx2("span", { className: "fillo-phone-caret", "aria-hidden": "true", children: "\u25BE" })
|
|
566
691
|
]
|
|
567
692
|
}
|
|
568
693
|
),
|
|
569
|
-
open && /* @__PURE__ */
|
|
570
|
-
/* @__PURE__ */
|
|
694
|
+
open && /* @__PURE__ */ jsxs2("div", { ref: popoverRef, className: `fillo-phone-popover fillo-phone-popover--${placement}`, children: [
|
|
695
|
+
/* @__PURE__ */ jsx2(
|
|
571
696
|
"input",
|
|
572
697
|
{
|
|
573
698
|
ref: searchRef,
|
|
@@ -588,8 +713,8 @@ function CountrySelect({
|
|
|
588
713
|
onKeyDown: onSearchKey
|
|
589
714
|
}
|
|
590
715
|
),
|
|
591
|
-
/* @__PURE__ */
|
|
592
|
-
matches.map((c, i) => /* @__PURE__ */
|
|
716
|
+
/* @__PURE__ */ jsxs2("ul", { className: "fillo-phone-list", role: "listbox", id: listId, "aria-label": "Country", children: [
|
|
717
|
+
matches.map((c, i) => /* @__PURE__ */ jsxs2(
|
|
593
718
|
"li",
|
|
594
719
|
{
|
|
595
720
|
id: `${listId}-opt-${i}`,
|
|
@@ -602,9 +727,9 @@ function CountrySelect({
|
|
|
602
727
|
choose(c);
|
|
603
728
|
},
|
|
604
729
|
children: [
|
|
605
|
-
/* @__PURE__ */
|
|
606
|
-
/* @__PURE__ */
|
|
607
|
-
/* @__PURE__ */
|
|
730
|
+
/* @__PURE__ */ jsx2("span", { className: "fillo-phone-flag-emoji", "aria-hidden": "true", children: flagEmoji(c.iso2) }),
|
|
731
|
+
/* @__PURE__ */ jsx2("span", { className: "fillo-phone-option-name", children: c.name }),
|
|
732
|
+
/* @__PURE__ */ jsxs2("span", { className: "fillo-phone-option-dial", children: [
|
|
608
733
|
"+",
|
|
609
734
|
c.dialCode
|
|
610
735
|
] })
|
|
@@ -612,7 +737,7 @@ function CountrySelect({
|
|
|
612
737
|
},
|
|
613
738
|
c.iso2
|
|
614
739
|
)),
|
|
615
|
-
matches.length === 0 && /* @__PURE__ */
|
|
740
|
+
matches.length === 0 && /* @__PURE__ */ jsx2("li", { className: "fillo-phone-empty", children: "No matches" })
|
|
616
741
|
] })
|
|
617
742
|
] })
|
|
618
743
|
] });
|
|
@@ -620,7 +745,7 @@ function CountrySelect({
|
|
|
620
745
|
|
|
621
746
|
// src/signature.tsx
|
|
622
747
|
import { useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
|
|
623
|
-
import { jsx as
|
|
748
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
624
749
|
function SignatureField({ field, value, error, setValue, ids: providedIds }) {
|
|
625
750
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
626
751
|
const slots = useFieldSlots(field, error);
|
|
@@ -725,21 +850,21 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
725
850
|
setValue(null);
|
|
726
851
|
}
|
|
727
852
|
const describedBy = [field.description ? ids.descriptionId : null, error ? ids.errorId : null].filter(Boolean).join(" ") || void 0;
|
|
728
|
-
return /* @__PURE__ */
|
|
729
|
-
/* @__PURE__ */
|
|
853
|
+
return /* @__PURE__ */ jsxs3("div", { ...slots.wrapperProps(`fillo-field fillo-field--signature${error ? " fillo-field--error" : ""}`), children: [
|
|
854
|
+
/* @__PURE__ */ jsxs3("span", { className: slots.label, "data-fillo": "label", id: ids.labelId, children: [
|
|
730
855
|
field.label,
|
|
731
|
-
!field.required && /* @__PURE__ */
|
|
856
|
+
!field.required && /* @__PURE__ */ jsx3("span", { className: "fillo-optional", children: strings.optional })
|
|
732
857
|
] }),
|
|
733
|
-
field.description && /* @__PURE__ */
|
|
734
|
-
/* @__PURE__ */
|
|
858
|
+
field.description && /* @__PURE__ */ jsx3("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
859
|
+
/* @__PURE__ */ jsxs3(
|
|
735
860
|
"div",
|
|
736
861
|
{
|
|
737
862
|
role: "group",
|
|
738
863
|
"aria-labelledby": ids.labelId,
|
|
739
864
|
"aria-describedby": describedBy,
|
|
740
865
|
children: [
|
|
741
|
-
/* @__PURE__ */
|
|
742
|
-
/* @__PURE__ */
|
|
866
|
+
/* @__PURE__ */ jsxs3("div", { className: slots.control("fillo-signature"), "data-fillo": "control", children: [
|
|
867
|
+
/* @__PURE__ */ jsx3(
|
|
743
868
|
"canvas",
|
|
744
869
|
{
|
|
745
870
|
ref: canvasRef,
|
|
@@ -752,12 +877,12 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
752
877
|
onPointerLeave: end
|
|
753
878
|
}
|
|
754
879
|
),
|
|
755
|
-
hasInk && /* @__PURE__ */
|
|
756
|
-
!hasInk && /* @__PURE__ */
|
|
880
|
+
hasInk && /* @__PURE__ */ jsx3("button", { type: "button", className: "fillo-signature-clear", onClick: clear, children: "Clear" }),
|
|
881
|
+
!hasInk && /* @__PURE__ */ jsx3("span", { className: "fillo-signature-hint", "aria-hidden": "true", children: "Sign here" })
|
|
757
882
|
] }),
|
|
758
|
-
/* @__PURE__ */
|
|
759
|
-
/* @__PURE__ */
|
|
760
|
-
/* @__PURE__ */
|
|
883
|
+
/* @__PURE__ */ jsxs3("div", { className: "fillo-signature-type", children: [
|
|
884
|
+
/* @__PURE__ */ jsx3("label", { className: "fillo-signature-type-label", htmlFor: typedInputId, children: "Or type your full name to sign" }),
|
|
885
|
+
/* @__PURE__ */ jsx3(
|
|
761
886
|
"input",
|
|
762
887
|
{
|
|
763
888
|
id: typedInputId,
|
|
@@ -779,13 +904,13 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
|
|
|
779
904
|
]
|
|
780
905
|
}
|
|
781
906
|
),
|
|
782
|
-
error && /* @__PURE__ */
|
|
907
|
+
error && /* @__PURE__ */ jsx3("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
783
908
|
] });
|
|
784
909
|
}
|
|
785
910
|
|
|
786
911
|
// src/upload.tsx
|
|
787
912
|
import { useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
|
|
788
|
-
import { Fragment, jsx as
|
|
913
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
789
914
|
function uploadStatus(inFlight, doneCount, strings) {
|
|
790
915
|
const failed = inFlight.filter((f) => f.error).length;
|
|
791
916
|
const active = inFlight.length - failed;
|
|
@@ -805,6 +930,7 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
805
930
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
806
931
|
const slots = useFieldSlots(field, error);
|
|
807
932
|
const strings = useStrings();
|
|
933
|
+
const chrome = useFilloChrome();
|
|
808
934
|
const schema = field;
|
|
809
935
|
const inputRef = useRef5(null);
|
|
810
936
|
const [inFlight, setInFlight] = useState4([]);
|
|
@@ -828,7 +954,9 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
828
954
|
const maxFiles = schema.maxFiles ?? 1;
|
|
829
955
|
const maxMb = schema.maxFileSizeMb ?? 500;
|
|
830
956
|
const maxBytes = maxMb * 1024 * 1024;
|
|
831
|
-
const
|
|
957
|
+
const storageBlocked = chrome?.uploadsAvailable === false;
|
|
958
|
+
const storageUrl = storageBlocked && chrome?.devChrome ? safeHttpUrl(chrome.warningUrl) : null;
|
|
959
|
+
const canUpload = Boolean(api.client && api.formId) && !storageBlocked;
|
|
832
960
|
const activeInFlight = inFlight.filter((f) => !f.error).length;
|
|
833
961
|
const remaining = maxFiles - files.length - activeInFlight;
|
|
834
962
|
async function startUpload(file) {
|
|
@@ -890,13 +1018,13 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
890
1018
|
setValue(next);
|
|
891
1019
|
}
|
|
892
1020
|
const describedBy = [field.description ? ids.descriptionId : null, error ? ids.errorId : null].filter(Boolean).join(" ") || void 0;
|
|
893
|
-
return /* @__PURE__ */
|
|
894
|
-
/* @__PURE__ */
|
|
1021
|
+
return /* @__PURE__ */ jsxs4("div", { ...slots.wrapperProps(`fillo-field fillo-field--file_upload${error ? " fillo-field--error" : ""}`), children: [
|
|
1022
|
+
/* @__PURE__ */ jsxs4("label", { className: slots.label, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
|
|
895
1023
|
field.label,
|
|
896
|
-
!field.required && /* @__PURE__ */
|
|
1024
|
+
!field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children: strings.optional })
|
|
897
1025
|
] }),
|
|
898
|
-
field.description && /* @__PURE__ */
|
|
899
|
-
remaining > 0 && /* @__PURE__ */
|
|
1026
|
+
field.description && /* @__PURE__ */ jsx4("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
1027
|
+
remaining > 0 && /* @__PURE__ */ jsxs4(
|
|
900
1028
|
"div",
|
|
901
1029
|
{
|
|
902
1030
|
"data-fillo": "control",
|
|
@@ -927,7 +1055,7 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
927
1055
|
}
|
|
928
1056
|
},
|
|
929
1057
|
children: [
|
|
930
|
-
/* @__PURE__ */
|
|
1058
|
+
/* @__PURE__ */ jsx4(
|
|
931
1059
|
"input",
|
|
932
1060
|
{
|
|
933
1061
|
ref: inputRef,
|
|
@@ -940,14 +1068,20 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
940
1068
|
onChange: (e) => canUpload && handleFiles(e.target.files)
|
|
941
1069
|
}
|
|
942
1070
|
),
|
|
943
|
-
canUpload ? /* @__PURE__ */
|
|
944
|
-
/* @__PURE__ */
|
|
945
|
-
/* @__PURE__ */
|
|
946
|
-
] }) : /* @__PURE__ */
|
|
1071
|
+
canUpload ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1072
|
+
/* @__PURE__ */ jsx4("span", { className: "fillo-dropzone-title", children: strings.dropzoneTitle(maxFiles > 1) }),
|
|
1073
|
+
/* @__PURE__ */ jsx4("span", { className: "fillo-dropzone-hint", children: strings.dropzoneHint(maxMb) })
|
|
1074
|
+
] }) : storageBlocked && chrome?.devChrome ? /* @__PURE__ */ jsxs4("span", { className: "fillo-dropzone-hint", children: [
|
|
1075
|
+
"Connect file storage to enable uploads",
|
|
1076
|
+
storageUrl && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1077
|
+
" \u2014 ",
|
|
1078
|
+
/* @__PURE__ */ jsx4("a", { href: storageUrl, target: "_blank", rel: "noopener noreferrer", children: storageUrl })
|
|
1079
|
+
] })
|
|
1080
|
+
] }) : /* @__PURE__ */ jsx4("span", { className: "fillo-dropzone-hint", children: storageBlocked ? strings.uploadsUnavailable : strings.uploadsDisabled })
|
|
947
1081
|
]
|
|
948
1082
|
}
|
|
949
1083
|
),
|
|
950
|
-
/* @__PURE__ */
|
|
1084
|
+
/* @__PURE__ */ jsx4(
|
|
951
1085
|
"div",
|
|
952
1086
|
{
|
|
953
1087
|
role: "status",
|
|
@@ -957,11 +1091,11 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
957
1091
|
children: uploadStatus(inFlight, files.length, strings)
|
|
958
1092
|
}
|
|
959
1093
|
),
|
|
960
|
-
(inFlight.length > 0 || files.length > 0) && /* @__PURE__ */
|
|
961
|
-
files.map((f) => /* @__PURE__ */
|
|
962
|
-
/* @__PURE__ */
|
|
963
|
-
/* @__PURE__ */
|
|
964
|
-
/* @__PURE__ */
|
|
1094
|
+
(inFlight.length > 0 || files.length > 0) && /* @__PURE__ */ jsxs4("ul", { className: "fillo-files", children: [
|
|
1095
|
+
files.map((f) => /* @__PURE__ */ jsxs4("li", { className: "fillo-file fillo-file--done", children: [
|
|
1096
|
+
/* @__PURE__ */ jsx4("span", { className: "fillo-file-name", children: f.name }),
|
|
1097
|
+
/* @__PURE__ */ jsx4("span", { className: "fillo-file-meta", children: formatBytes(f.size) }),
|
|
1098
|
+
/* @__PURE__ */ jsx4(
|
|
965
1099
|
"button",
|
|
966
1100
|
{
|
|
967
1101
|
type: "button",
|
|
@@ -972,7 +1106,7 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
972
1106
|
}
|
|
973
1107
|
)
|
|
974
1108
|
] }, f.fileId)),
|
|
975
|
-
inFlight.map((f) => /* @__PURE__ */
|
|
1109
|
+
inFlight.map((f) => /* @__PURE__ */ jsxs4(
|
|
976
1110
|
"li",
|
|
977
1111
|
{
|
|
978
1112
|
className: `fillo-file${f.error ? " fillo-file--failed" : ""}`,
|
|
@@ -984,10 +1118,10 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
984
1118
|
"aria-valuenow": Math.round(f.fraction * 100)
|
|
985
1119
|
},
|
|
986
1120
|
children: [
|
|
987
|
-
/* @__PURE__ */
|
|
988
|
-
f.error ? /* @__PURE__ */
|
|
989
|
-
/* @__PURE__ */
|
|
990
|
-
f.file && !f.tooLarge && remaining > 0 && /* @__PURE__ */
|
|
1121
|
+
/* @__PURE__ */ jsx4("span", { className: "fillo-file-name", children: f.name }),
|
|
1122
|
+
f.error ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1123
|
+
/* @__PURE__ */ jsx4("span", { className: "fillo-file-meta", children: f.error }),
|
|
1124
|
+
f.file && !f.tooLarge && remaining > 0 && /* @__PURE__ */ jsx4(
|
|
991
1125
|
"button",
|
|
992
1126
|
{
|
|
993
1127
|
type: "button",
|
|
@@ -1002,7 +1136,7 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
1002
1136
|
children: strings.uploadRetry
|
|
1003
1137
|
}
|
|
1004
1138
|
),
|
|
1005
|
-
/* @__PURE__ */
|
|
1139
|
+
/* @__PURE__ */ jsx4(
|
|
1006
1140
|
"button",
|
|
1007
1141
|
{
|
|
1008
1142
|
type: "button",
|
|
@@ -1012,14 +1146,14 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
1012
1146
|
children: "\xD7"
|
|
1013
1147
|
}
|
|
1014
1148
|
)
|
|
1015
|
-
] }) : /* @__PURE__ */
|
|
1016
|
-
/* @__PURE__ */
|
|
1149
|
+
] }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1150
|
+
/* @__PURE__ */ jsxs4("span", { className: "fillo-file-meta", children: [
|
|
1017
1151
|
Math.round(f.fraction * 100),
|
|
1018
1152
|
"% of ",
|
|
1019
1153
|
formatBytes(f.size)
|
|
1020
1154
|
] }),
|
|
1021
|
-
/* @__PURE__ */
|
|
1022
|
-
/* @__PURE__ */
|
|
1155
|
+
/* @__PURE__ */ jsx4("span", { className: "fillo-progress", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("span", { className: "fillo-progress-bar", style: { width: `${f.fraction * 100}%` } }) }),
|
|
1156
|
+
/* @__PURE__ */ jsx4(
|
|
1023
1157
|
"button",
|
|
1024
1158
|
{
|
|
1025
1159
|
type: "button",
|
|
@@ -1035,12 +1169,12 @@ function FileUploadField({ field, value, error, setValue, api, ids: providedIds
|
|
|
1035
1169
|
f.key
|
|
1036
1170
|
))
|
|
1037
1171
|
] }),
|
|
1038
|
-
error && /* @__PURE__ */
|
|
1172
|
+
error && /* @__PURE__ */ jsx4("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
1039
1173
|
] });
|
|
1040
1174
|
}
|
|
1041
1175
|
|
|
1042
1176
|
// src/fields.tsx
|
|
1043
|
-
import { jsx as
|
|
1177
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1044
1178
|
function fieldAria(field, error, ids) {
|
|
1045
1179
|
const describedBy = [
|
|
1046
1180
|
field.description ? ids.descriptionId : null,
|
|
@@ -1087,7 +1221,7 @@ function FieldShell({
|
|
|
1087
1221
|
const descriptionCls = useSlotClass("fillo-description", { slot: "fieldDescription", ...state });
|
|
1088
1222
|
const errorCls = useSlotClass("fillo-error", { slot: "error", ...state });
|
|
1089
1223
|
const strings = useStrings();
|
|
1090
|
-
return /* @__PURE__ */
|
|
1224
|
+
return /* @__PURE__ */ jsxs5(
|
|
1091
1225
|
"div",
|
|
1092
1226
|
{
|
|
1093
1227
|
className: fieldCls,
|
|
@@ -1097,13 +1231,13 @@ function FieldShell({
|
|
|
1097
1231
|
"data-invalid": error ? "" : void 0,
|
|
1098
1232
|
"data-required": field.required ? "" : void 0,
|
|
1099
1233
|
children: [
|
|
1100
|
-
/* @__PURE__ */
|
|
1234
|
+
/* @__PURE__ */ jsxs5("label", { className: labelCls, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
|
|
1101
1235
|
field.label,
|
|
1102
|
-
!field.required && /* @__PURE__ */
|
|
1236
|
+
!field.required && /* @__PURE__ */ jsx5("span", { className: "fillo-optional", children: strings.optional })
|
|
1103
1237
|
] }),
|
|
1104
|
-
field.description && /* @__PURE__ */
|
|
1238
|
+
field.description && /* @__PURE__ */ jsx5("p", { className: descriptionCls, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
1105
1239
|
children,
|
|
1106
|
-
error && /* @__PURE__ */
|
|
1240
|
+
error && /* @__PURE__ */ jsx5("p", { className: errorCls, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
1107
1241
|
]
|
|
1108
1242
|
}
|
|
1109
1243
|
);
|
|
@@ -1112,7 +1246,7 @@ function TextInput({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1112
1246
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
1113
1247
|
const type = field.kind === "email" ? "email" : field.kind === "url" ? "url" : field.kind === "number" ? "number" : field.kind === "date" ? "date" : "text";
|
|
1114
1248
|
const control = useControl("fillo-input", field, error);
|
|
1115
|
-
return /* @__PURE__ */
|
|
1249
|
+
return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
|
|
1116
1250
|
"input",
|
|
1117
1251
|
{
|
|
1118
1252
|
id: ids.inputId,
|
|
@@ -1131,7 +1265,7 @@ function TextInput({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1131
1265
|
function LongText({ field, value, error, setValue, ids: providedIds }) {
|
|
1132
1266
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
1133
1267
|
const control = useControl("fillo-input fillo-textarea", field, error);
|
|
1134
|
-
return /* @__PURE__ */
|
|
1268
|
+
return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
|
|
1135
1269
|
"textarea",
|
|
1136
1270
|
{
|
|
1137
1271
|
id: ids.inputId,
|
|
@@ -1168,7 +1302,7 @@ var OPTION_ICON_PATHS = {
|
|
|
1168
1302
|
};
|
|
1169
1303
|
function ChoiceIcon({ icon }) {
|
|
1170
1304
|
if (!icon) return null;
|
|
1171
|
-
return /* @__PURE__ */
|
|
1305
|
+
return /* @__PURE__ */ jsx5(
|
|
1172
1306
|
"svg",
|
|
1173
1307
|
{
|
|
1174
1308
|
className: "fillo-option-icon",
|
|
@@ -1179,7 +1313,7 @@ function ChoiceIcon({ icon }) {
|
|
|
1179
1313
|
strokeLinecap: "round",
|
|
1180
1314
|
strokeLinejoin: "round",
|
|
1181
1315
|
"aria-hidden": "true",
|
|
1182
|
-
children: /* @__PURE__ */
|
|
1316
|
+
children: /* @__PURE__ */ jsx5("path", { d: OPTION_ICON_PATHS[icon] })
|
|
1183
1317
|
}
|
|
1184
1318
|
);
|
|
1185
1319
|
}
|
|
@@ -1193,7 +1327,7 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1193
1327
|
const isOtherValue = typeof value === "string" && value !== "" && !choice.options.some((o) => o.id === value);
|
|
1194
1328
|
const [otherOn, setOtherOn] = useState5(isOtherValue);
|
|
1195
1329
|
const otherActive = isOtherValue || value === "" && otherOn;
|
|
1196
|
-
return /* @__PURE__ */
|
|
1330
|
+
return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs5(
|
|
1197
1331
|
"div",
|
|
1198
1332
|
{
|
|
1199
1333
|
className: slotClass2("fillo-options", appearance, { slot: "options", kind: field.kind, fieldId: field.id }),
|
|
@@ -1202,7 +1336,7 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1202
1336
|
"aria-labelledby": ids.labelId,
|
|
1203
1337
|
...fieldAria(field, error, ids),
|
|
1204
1338
|
children: [
|
|
1205
|
-
options.map((opt) => /* @__PURE__ */
|
|
1339
|
+
options.map((opt) => /* @__PURE__ */ jsxs5(
|
|
1206
1340
|
"label",
|
|
1207
1341
|
{
|
|
1208
1342
|
"data-option": opt.id,
|
|
@@ -1220,7 +1354,7 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1220
1354
|
setValue(opt.id);
|
|
1221
1355
|
},
|
|
1222
1356
|
children: [
|
|
1223
|
-
/* @__PURE__ */
|
|
1357
|
+
/* @__PURE__ */ jsx5(
|
|
1224
1358
|
"input",
|
|
1225
1359
|
{
|
|
1226
1360
|
type: "radio",
|
|
@@ -1233,15 +1367,15 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1233
1367
|
}
|
|
1234
1368
|
}
|
|
1235
1369
|
),
|
|
1236
|
-
/* @__PURE__ */
|
|
1237
|
-
/* @__PURE__ */
|
|
1370
|
+
/* @__PURE__ */ jsx5(ChoiceIcon, { icon: opt.icon }),
|
|
1371
|
+
/* @__PURE__ */ jsx5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
|
|
1238
1372
|
]
|
|
1239
1373
|
},
|
|
1240
1374
|
opt.id
|
|
1241
1375
|
)),
|
|
1242
1376
|
choice.allowOther && // A <div>, not a <label>, so editing the free-text input below can't
|
|
1243
1377
|
// toggle the radio. The radio + "Other" text keep their own <label>.
|
|
1244
|
-
/* @__PURE__ */
|
|
1378
|
+
/* @__PURE__ */ jsxs5(
|
|
1245
1379
|
"div",
|
|
1246
1380
|
{
|
|
1247
1381
|
"data-fillo": "option",
|
|
@@ -1252,8 +1386,8 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1252
1386
|
otherActive
|
|
1253
1387
|
),
|
|
1254
1388
|
children: [
|
|
1255
|
-
/* @__PURE__ */
|
|
1256
|
-
/* @__PURE__ */
|
|
1389
|
+
/* @__PURE__ */ jsxs5("label", { className: "fillo-option-main", children: [
|
|
1390
|
+
/* @__PURE__ */ jsx5(
|
|
1257
1391
|
"input",
|
|
1258
1392
|
{
|
|
1259
1393
|
type: "radio",
|
|
@@ -1266,9 +1400,9 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1266
1400
|
}
|
|
1267
1401
|
}
|
|
1268
1402
|
),
|
|
1269
|
-
/* @__PURE__ */
|
|
1403
|
+
/* @__PURE__ */ jsx5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
|
|
1270
1404
|
] }),
|
|
1271
|
-
otherActive && /* @__PURE__ */
|
|
1405
|
+
otherActive && /* @__PURE__ */ jsx5(
|
|
1272
1406
|
"input",
|
|
1273
1407
|
{
|
|
1274
1408
|
type: "text",
|
|
@@ -1300,7 +1434,7 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1300
1434
|
const toggle = (id) => setValue(selected.includes(id) ? selected.filter((v) => v !== id) : [...selected, id]);
|
|
1301
1435
|
const appearance = useFilloAppearance();
|
|
1302
1436
|
const optionCls = (base, optionId, isSelected) => slotClass2(base, appearance, { slot: "option", kind: field.kind, fieldId: field.id, optionId, selected: isSelected });
|
|
1303
|
-
return /* @__PURE__ */
|
|
1437
|
+
return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs5(
|
|
1304
1438
|
"div",
|
|
1305
1439
|
{
|
|
1306
1440
|
className: slotClass2("fillo-options", appearance, { slot: "options", kind: field.kind, fieldId: field.id }),
|
|
@@ -1309,7 +1443,7 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1309
1443
|
"aria-labelledby": ids.labelId,
|
|
1310
1444
|
...fieldAria(field, error, ids),
|
|
1311
1445
|
children: [
|
|
1312
|
-
options.map((opt) => /* @__PURE__ */
|
|
1446
|
+
options.map((opt) => /* @__PURE__ */ jsxs5(
|
|
1313
1447
|
"label",
|
|
1314
1448
|
{
|
|
1315
1449
|
"data-option": opt.id,
|
|
@@ -1326,7 +1460,7 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1326
1460
|
toggle(opt.id);
|
|
1327
1461
|
},
|
|
1328
1462
|
children: [
|
|
1329
|
-
/* @__PURE__ */
|
|
1463
|
+
/* @__PURE__ */ jsx5(
|
|
1330
1464
|
"input",
|
|
1331
1465
|
{
|
|
1332
1466
|
type: "checkbox",
|
|
@@ -1335,15 +1469,15 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1335
1469
|
onChange: () => toggle(opt.id)
|
|
1336
1470
|
}
|
|
1337
1471
|
),
|
|
1338
|
-
/* @__PURE__ */
|
|
1339
|
-
/* @__PURE__ */
|
|
1472
|
+
/* @__PURE__ */ jsx5(ChoiceIcon, { icon: opt.icon }),
|
|
1473
|
+
/* @__PURE__ */ jsx5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
|
|
1340
1474
|
]
|
|
1341
1475
|
},
|
|
1342
1476
|
opt.id
|
|
1343
1477
|
)),
|
|
1344
1478
|
choice.allowOther && // A <div>, not a <label>, so editing the free-text input below can't
|
|
1345
1479
|
// toggle the checkbox. The checkbox + "Other" text keep their own <label>.
|
|
1346
|
-
/* @__PURE__ */
|
|
1480
|
+
/* @__PURE__ */ jsxs5(
|
|
1347
1481
|
"div",
|
|
1348
1482
|
{
|
|
1349
1483
|
"data-fillo": "option",
|
|
@@ -1354,8 +1488,8 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1354
1488
|
otherActive
|
|
1355
1489
|
),
|
|
1356
1490
|
children: [
|
|
1357
|
-
/* @__PURE__ */
|
|
1358
|
-
/* @__PURE__ */
|
|
1491
|
+
/* @__PURE__ */ jsxs5("label", { className: "fillo-option-main", children: [
|
|
1492
|
+
/* @__PURE__ */ jsx5(
|
|
1359
1493
|
"input",
|
|
1360
1494
|
{
|
|
1361
1495
|
type: "checkbox",
|
|
@@ -1371,9 +1505,9 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1371
1505
|
}
|
|
1372
1506
|
}
|
|
1373
1507
|
),
|
|
1374
|
-
/* @__PURE__ */
|
|
1508
|
+
/* @__PURE__ */ jsx5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
|
|
1375
1509
|
] }),
|
|
1376
|
-
otherActive && /* @__PURE__ */
|
|
1510
|
+
otherActive && /* @__PURE__ */ jsx5(
|
|
1377
1511
|
"input",
|
|
1378
1512
|
{
|
|
1379
1513
|
type: "text",
|
|
@@ -1411,9 +1545,9 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1411
1545
|
const [otherOn, setOtherOn] = useState5(isOtherValue);
|
|
1412
1546
|
const otherActive = isOtherValue || value === "" && otherOn;
|
|
1413
1547
|
const control = useControl("fillo-input fillo-select", field, error);
|
|
1414
|
-
return /* @__PURE__ */
|
|
1415
|
-
/* @__PURE__ */
|
|
1416
|
-
/* @__PURE__ */
|
|
1548
|
+
return /* @__PURE__ */ jsxs5(FieldShell, { field, error, ids, children: [
|
|
1549
|
+
/* @__PURE__ */ jsxs5("div", { className: "fillo-select-wrap", children: [
|
|
1550
|
+
/* @__PURE__ */ jsxs5(
|
|
1417
1551
|
"select",
|
|
1418
1552
|
{
|
|
1419
1553
|
id: ids.inputId,
|
|
@@ -1430,15 +1564,15 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1430
1564
|
}
|
|
1431
1565
|
},
|
|
1432
1566
|
children: [
|
|
1433
|
-
/* @__PURE__ */
|
|
1434
|
-
options.map((opt) => /* @__PURE__ */
|
|
1435
|
-
choice.allowOther && /* @__PURE__ */
|
|
1567
|
+
/* @__PURE__ */ jsx5("option", { value: "", children: field.placeholder ?? strings.choosePlaceholder }),
|
|
1568
|
+
options.map((opt) => /* @__PURE__ */ jsx5("option", { value: opt.id, children: opt.label }, opt.id)),
|
|
1569
|
+
choice.allowOther && /* @__PURE__ */ jsx5("option", { value: otherValue, children: `${strings.other}\u2026` })
|
|
1436
1570
|
]
|
|
1437
1571
|
}
|
|
1438
1572
|
),
|
|
1439
|
-
/* @__PURE__ */
|
|
1573
|
+
/* @__PURE__ */ jsx5("span", { className: "fillo-select-icon", "aria-hidden": "true" })
|
|
1440
1574
|
] }),
|
|
1441
|
-
otherActive && /* @__PURE__ */
|
|
1575
|
+
otherActive && /* @__PURE__ */ jsx5(
|
|
1442
1576
|
"input",
|
|
1443
1577
|
{
|
|
1444
1578
|
type: "text",
|
|
@@ -1459,19 +1593,19 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1459
1593
|
const slots = useFieldSlots(field, error);
|
|
1460
1594
|
const checkbox = field;
|
|
1461
1595
|
if (checkbox.appearance === "toggle") {
|
|
1462
|
-
return /* @__PURE__ */
|
|
1596
|
+
return /* @__PURE__ */ jsxs5(
|
|
1463
1597
|
"div",
|
|
1464
1598
|
{
|
|
1465
1599
|
...slots.wrapperProps(
|
|
1466
1600
|
`fillo-field fillo-field--checkbox fillo-field--toggle${error ? " fillo-field--error" : ""}`
|
|
1467
1601
|
),
|
|
1468
1602
|
children: [
|
|
1469
|
-
/* @__PURE__ */
|
|
1470
|
-
/* @__PURE__ */
|
|
1603
|
+
/* @__PURE__ */ jsxs5("label", { className: "fillo-toggle", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
|
|
1604
|
+
/* @__PURE__ */ jsx5("span", { className: "fillo-toggle-copy", children: /* @__PURE__ */ jsxs5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: [
|
|
1471
1605
|
field.label,
|
|
1472
|
-
!field.required && /* @__PURE__ */
|
|
1606
|
+
!field.required && /* @__PURE__ */ jsx5("span", { className: "fillo-optional", children: strings.optional })
|
|
1473
1607
|
] }) }),
|
|
1474
|
-
/* @__PURE__ */
|
|
1608
|
+
/* @__PURE__ */ jsx5(
|
|
1475
1609
|
"input",
|
|
1476
1610
|
{
|
|
1477
1611
|
id: ids.inputId,
|
|
@@ -1484,23 +1618,23 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1484
1618
|
...fieldAria(field, error, ids)
|
|
1485
1619
|
}
|
|
1486
1620
|
),
|
|
1487
|
-
/* @__PURE__ */
|
|
1621
|
+
/* @__PURE__ */ jsx5("span", { className: "fillo-toggle-track", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("span", { className: "fillo-toggle-thumb" }) })
|
|
1488
1622
|
] }),
|
|
1489
|
-
field.description && /* @__PURE__ */
|
|
1490
|
-
error && /* @__PURE__ */
|
|
1623
|
+
field.description && /* @__PURE__ */ jsx5("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
1624
|
+
error && /* @__PURE__ */ jsx5("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
1491
1625
|
]
|
|
1492
1626
|
}
|
|
1493
1627
|
);
|
|
1494
1628
|
}
|
|
1495
|
-
return /* @__PURE__ */
|
|
1629
|
+
return /* @__PURE__ */ jsxs5(
|
|
1496
1630
|
"div",
|
|
1497
1631
|
{
|
|
1498
1632
|
...slots.wrapperProps(
|
|
1499
1633
|
`fillo-field fillo-field--checkbox${error ? " fillo-field--error" : ""}`
|
|
1500
1634
|
),
|
|
1501
1635
|
children: [
|
|
1502
|
-
/* @__PURE__ */
|
|
1503
|
-
/* @__PURE__ */
|
|
1636
|
+
/* @__PURE__ */ jsxs5("label", { className: "fillo-option", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
|
|
1637
|
+
/* @__PURE__ */ jsx5(
|
|
1504
1638
|
"input",
|
|
1505
1639
|
{
|
|
1506
1640
|
id: ids.inputId,
|
|
@@ -1511,13 +1645,13 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1511
1645
|
...fieldAria(field, error, ids)
|
|
1512
1646
|
}
|
|
1513
1647
|
),
|
|
1514
|
-
/* @__PURE__ */
|
|
1648
|
+
/* @__PURE__ */ jsxs5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: [
|
|
1515
1649
|
field.label,
|
|
1516
|
-
!field.required && /* @__PURE__ */
|
|
1650
|
+
!field.required && /* @__PURE__ */ jsx5("span", { className: "fillo-optional", children: strings.optional })
|
|
1517
1651
|
] })
|
|
1518
1652
|
] }),
|
|
1519
|
-
field.description && /* @__PURE__ */
|
|
1520
|
-
error && /* @__PURE__ */
|
|
1653
|
+
field.description && /* @__PURE__ */ jsx5("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
|
|
1654
|
+
error && /* @__PURE__ */ jsx5("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, role: "alert", children: error })
|
|
1521
1655
|
]
|
|
1522
1656
|
}
|
|
1523
1657
|
);
|
|
@@ -1556,7 +1690,7 @@ function Rating({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1556
1690
|
const tabbable = rovingIndex(steps, selected);
|
|
1557
1691
|
const appearance = useFilloAppearance();
|
|
1558
1692
|
const control = useControl("fillo-rating", field, error);
|
|
1559
|
-
return /* @__PURE__ */
|
|
1693
|
+
return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
|
|
1560
1694
|
"div",
|
|
1561
1695
|
{
|
|
1562
1696
|
...control,
|
|
@@ -1564,7 +1698,7 @@ function Rating({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1564
1698
|
"aria-labelledby": ids.labelId,
|
|
1565
1699
|
...fieldAria(field, error, ids),
|
|
1566
1700
|
onKeyDown: (e) => radiogroupKeyDown(e, steps, selected, (n) => setValue(n)),
|
|
1567
|
-
children: steps.map((n, i) => /* @__PURE__ */
|
|
1701
|
+
children: steps.map((n, i) => /* @__PURE__ */ jsx5(
|
|
1568
1702
|
"button",
|
|
1569
1703
|
{
|
|
1570
1704
|
type: "button",
|
|
@@ -1607,8 +1741,8 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1607
1741
|
].filter(Boolean).join(" ") || void 0;
|
|
1608
1742
|
const appearance = useFilloAppearance();
|
|
1609
1743
|
const control = useControl("fillo-scale", field, error);
|
|
1610
|
-
return /* @__PURE__ */
|
|
1611
|
-
/* @__PURE__ */
|
|
1744
|
+
return /* @__PURE__ */ jsxs5(FieldShell, { field, error, ids, children: [
|
|
1745
|
+
/* @__PURE__ */ jsx5(
|
|
1612
1746
|
"div",
|
|
1613
1747
|
{
|
|
1614
1748
|
...control,
|
|
@@ -1618,7 +1752,7 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1618
1752
|
"aria-required": field.required ? true : void 0,
|
|
1619
1753
|
"aria-describedby": describedBy,
|
|
1620
1754
|
onKeyDown: (e) => radiogroupKeyDown(e, steps, current, (n) => setValue(n)),
|
|
1621
|
-
children: steps.map((n, i) => /* @__PURE__ */
|
|
1755
|
+
children: steps.map((n, i) => /* @__PURE__ */ jsx5(
|
|
1622
1756
|
"button",
|
|
1623
1757
|
{
|
|
1624
1758
|
type: "button",
|
|
@@ -1641,9 +1775,9 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1641
1775
|
))
|
|
1642
1776
|
}
|
|
1643
1777
|
),
|
|
1644
|
-
hasLabels && /* @__PURE__ */
|
|
1645
|
-
/* @__PURE__ */
|
|
1646
|
-
/* @__PURE__ */
|
|
1778
|
+
hasLabels && /* @__PURE__ */ jsxs5("div", { className: "fillo-scale-labels", children: [
|
|
1779
|
+
/* @__PURE__ */ jsx5("span", { id: minLabelId, children: scale.minLabel }),
|
|
1780
|
+
/* @__PURE__ */ jsx5("span", { id: maxLabelId, children: scale.maxLabel })
|
|
1647
1781
|
] })
|
|
1648
1782
|
] });
|
|
1649
1783
|
}
|
|
@@ -1663,21 +1797,21 @@ function Ranking({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1663
1797
|
[next[index], next[target]] = [next[target], next[index]];
|
|
1664
1798
|
setValue(next);
|
|
1665
1799
|
}
|
|
1666
|
-
return /* @__PURE__ */
|
|
1800
|
+
return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
|
|
1667
1801
|
"div",
|
|
1668
1802
|
{
|
|
1669
1803
|
id: ids.inputId,
|
|
1670
1804
|
role: "group",
|
|
1671
1805
|
"aria-labelledby": ids.labelId,
|
|
1672
1806
|
...fieldAria(field, error, ids),
|
|
1673
|
-
children: /* @__PURE__ */
|
|
1807
|
+
children: /* @__PURE__ */ jsx5("ol", { className: "fillo-ranking", children: order.map((id, index) => {
|
|
1674
1808
|
const option = ranking.options.find((o) => o.id === id);
|
|
1675
1809
|
if (!option) return null;
|
|
1676
|
-
return /* @__PURE__ */
|
|
1677
|
-
/* @__PURE__ */
|
|
1678
|
-
/* @__PURE__ */
|
|
1679
|
-
/* @__PURE__ */
|
|
1680
|
-
/* @__PURE__ */
|
|
1810
|
+
return /* @__PURE__ */ jsxs5("li", { className: "fillo-ranking-item", children: [
|
|
1811
|
+
/* @__PURE__ */ jsx5("span", { className: "fillo-ranking-index", children: index + 1 }),
|
|
1812
|
+
/* @__PURE__ */ jsx5("span", { className: "fillo-ranking-label", children: option.label }),
|
|
1813
|
+
/* @__PURE__ */ jsxs5("span", { className: "fillo-ranking-controls", children: [
|
|
1814
|
+
/* @__PURE__ */ jsx5(
|
|
1681
1815
|
"button",
|
|
1682
1816
|
{
|
|
1683
1817
|
type: "button",
|
|
@@ -1688,7 +1822,7 @@ function Ranking({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1688
1822
|
children: "\u2191"
|
|
1689
1823
|
}
|
|
1690
1824
|
),
|
|
1691
|
-
/* @__PURE__ */
|
|
1825
|
+
/* @__PURE__ */ jsx5(
|
|
1692
1826
|
"button",
|
|
1693
1827
|
{
|
|
1694
1828
|
type: "button",
|
|
@@ -1709,7 +1843,7 @@ function Matrix({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1709
1843
|
const ids = useFilloFieldIds(field.id, providedIds);
|
|
1710
1844
|
const matrix = field;
|
|
1711
1845
|
const answers = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1712
|
-
return /* @__PURE__ */
|
|
1846
|
+
return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
|
|
1713
1847
|
"div",
|
|
1714
1848
|
{
|
|
1715
1849
|
id: ids.inputId,
|
|
@@ -1717,12 +1851,12 @@ function Matrix({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1717
1851
|
role: "group",
|
|
1718
1852
|
"aria-labelledby": ids.labelId,
|
|
1719
1853
|
...fieldAria(field, error, ids),
|
|
1720
|
-
children: /* @__PURE__ */
|
|
1721
|
-
/* @__PURE__ */
|
|
1722
|
-
/* @__PURE__ */
|
|
1723
|
-
matrix.columns.map((col) => /* @__PURE__ */
|
|
1854
|
+
children: /* @__PURE__ */ jsxs5("table", { className: "fillo-matrix", children: [
|
|
1855
|
+
/* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
|
|
1856
|
+
/* @__PURE__ */ jsx5("th", {}),
|
|
1857
|
+
matrix.columns.map((col) => /* @__PURE__ */ jsx5("th", { scope: "col", children: col.label }, col.id))
|
|
1724
1858
|
] }) }),
|
|
1725
|
-
/* @__PURE__ */
|
|
1859
|
+
/* @__PURE__ */ jsx5("tbody", { children: matrix.rows.map((row) => {
|
|
1726
1860
|
const rowLabelId = `${ids.inputId}-${row.id}`;
|
|
1727
1861
|
const answered = answers[row.id] !== void 0;
|
|
1728
1862
|
const rowInvalid = Boolean(error) && field.required && !answered;
|
|
@@ -1733,17 +1867,17 @@ function Matrix({ field, value, error, setValue, ids: providedIds }) {
|
|
|
1733
1867
|
// radiogroup lives on an inner element that aria-owns the radios
|
|
1734
1868
|
// scattered across the cells (they can't be DOM-nested inside a
|
|
1735
1869
|
// single element within a table row).
|
|
1736
|
-
/* @__PURE__ */
|
|
1737
|
-
/* @__PURE__ */
|
|
1870
|
+
/* @__PURE__ */ jsxs5("tr", { children: [
|
|
1871
|
+
/* @__PURE__ */ jsx5("th", { scope: "row", children: /* @__PURE__ */ jsx5(
|
|
1738
1872
|
"div",
|
|
1739
1873
|
{
|
|
1740
1874
|
role: "radiogroup",
|
|
1741
1875
|
"aria-labelledby": rowLabelId,
|
|
1742
1876
|
"aria-owns": matrix.columns.map(radioId).join(" "),
|
|
1743
|
-
children: /* @__PURE__ */
|
|
1877
|
+
children: /* @__PURE__ */ jsx5("span", { id: rowLabelId, children: row.label })
|
|
1744
1878
|
}
|
|
1745
1879
|
) }),
|
|
1746
|
-
matrix.columns.map((col) => /* @__PURE__ */
|
|
1880
|
+
matrix.columns.map((col) => /* @__PURE__ */ jsx5("td", { children: /* @__PURE__ */ jsx5(
|
|
1747
1881
|
"input",
|
|
1748
1882
|
{
|
|
1749
1883
|
id: radioId(col),
|
|
@@ -1790,11 +1924,11 @@ var DEFAULT_COMPONENTS = {
|
|
|
1790
1924
|
function ContentRenderer({ block }) {
|
|
1791
1925
|
switch (block.kind) {
|
|
1792
1926
|
case "heading":
|
|
1793
|
-
return /* @__PURE__ */
|
|
1927
|
+
return /* @__PURE__ */ jsx5("h3", { className: "fillo-heading", children: block.text });
|
|
1794
1928
|
case "paragraph":
|
|
1795
|
-
return /* @__PURE__ */
|
|
1929
|
+
return /* @__PURE__ */ jsx5("p", { className: "fillo-paragraph", children: block.text });
|
|
1796
1930
|
case "divider":
|
|
1797
|
-
return /* @__PURE__ */
|
|
1931
|
+
return /* @__PURE__ */ jsx5("hr", { className: "fillo-divider" });
|
|
1798
1932
|
}
|
|
1799
1933
|
}
|
|
1800
1934
|
function blockHasPiping(block) {
|
|
@@ -1833,7 +1967,7 @@ function BlockRendererInner({ block, api, components, customComponents }) {
|
|
|
1833
1967
|
block = pipeBlock(block, api.data, api.form);
|
|
1834
1968
|
const ids = useFilloFieldIds(block.id);
|
|
1835
1969
|
const strings = useStrings();
|
|
1836
|
-
if (!isField(block)) return /* @__PURE__ */
|
|
1970
|
+
if (!isField(block)) return /* @__PURE__ */ jsx5(ContentRenderer, { block });
|
|
1837
1971
|
let Component;
|
|
1838
1972
|
if (block.kind === "custom") {
|
|
1839
1973
|
Component = customComponents?.[block.component] ?? components?.custom;
|
|
@@ -1851,7 +1985,7 @@ function BlockRendererInner({ block, api, components, customComponents }) {
|
|
|
1851
1985
|
}
|
|
1852
1986
|
const rawError = api.errors[block.id];
|
|
1853
1987
|
const error = rawError === REQUIRED_FIELD_MESSAGE ? strings.required : rawError;
|
|
1854
|
-
return /* @__PURE__ */
|
|
1988
|
+
return /* @__PURE__ */ jsx5(
|
|
1855
1989
|
Component,
|
|
1856
1990
|
{
|
|
1857
1991
|
field: block,
|
|
@@ -1888,7 +2022,7 @@ function FormField({
|
|
|
1888
2022
|
const block = api.form.pages.flatMap((p) => p.blocks).find((b) => b.id === id);
|
|
1889
2023
|
if (!block) return null;
|
|
1890
2024
|
if (isField(block) && !visibleIds.has(block.id)) return null;
|
|
1891
|
-
return /* @__PURE__ */
|
|
2025
|
+
return /* @__PURE__ */ jsx5(
|
|
1892
2026
|
BlockRenderer,
|
|
1893
2027
|
{
|
|
1894
2028
|
block,
|
|
@@ -1899,8 +2033,127 @@ function FormField({
|
|
|
1899
2033
|
);
|
|
1900
2034
|
}
|
|
1901
2035
|
|
|
2036
|
+
// src/turnstile.tsx
|
|
2037
|
+
import { useEffect as useEffect7, useRef as useRef6 } from "react";
|
|
2038
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
2039
|
+
var SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
2040
|
+
var SCRIPT_ID = "fillo-turnstile-script";
|
|
2041
|
+
var scriptPromise = null;
|
|
2042
|
+
function turnstileGlobal() {
|
|
2043
|
+
return globalThis.turnstile;
|
|
2044
|
+
}
|
|
2045
|
+
function loadTurnstileScript() {
|
|
2046
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
2047
|
+
return Promise.reject(new Error("Turnstile needs a browser"));
|
|
2048
|
+
}
|
|
2049
|
+
if (turnstileGlobal()) return Promise.resolve();
|
|
2050
|
+
if (scriptPromise) return scriptPromise;
|
|
2051
|
+
scriptPromise = new Promise((resolve, reject) => {
|
|
2052
|
+
const onReady = () => {
|
|
2053
|
+
if (turnstileGlobal()) resolve();
|
|
2054
|
+
else reject(new Error("Turnstile loaded without its global"));
|
|
2055
|
+
};
|
|
2056
|
+
const existing = document.getElementById(SCRIPT_ID);
|
|
2057
|
+
if (existing) {
|
|
2058
|
+
if (turnstileGlobal()) {
|
|
2059
|
+
resolve();
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
existing.addEventListener("load", onReady, { once: true });
|
|
2063
|
+
existing.addEventListener(
|
|
2064
|
+
"error",
|
|
2065
|
+
() => reject(new Error("Turnstile script failed to load")),
|
|
2066
|
+
{ once: true }
|
|
2067
|
+
);
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
const script = document.createElement("script");
|
|
2071
|
+
script.id = SCRIPT_ID;
|
|
2072
|
+
script.src = SCRIPT_SRC;
|
|
2073
|
+
script.async = true;
|
|
2074
|
+
script.defer = true;
|
|
2075
|
+
script.addEventListener("load", onReady, { once: true });
|
|
2076
|
+
script.addEventListener(
|
|
2077
|
+
"error",
|
|
2078
|
+
() => {
|
|
2079
|
+
script.remove();
|
|
2080
|
+
scriptPromise = null;
|
|
2081
|
+
reject(new Error("Turnstile script failed to load"));
|
|
2082
|
+
},
|
|
2083
|
+
{ once: true }
|
|
2084
|
+
);
|
|
2085
|
+
document.head.appendChild(script);
|
|
2086
|
+
});
|
|
2087
|
+
return scriptPromise;
|
|
2088
|
+
}
|
|
2089
|
+
function TurnstileWidget({
|
|
2090
|
+
siteKey,
|
|
2091
|
+
onToken,
|
|
2092
|
+
onError,
|
|
2093
|
+
registerReset,
|
|
2094
|
+
className
|
|
2095
|
+
}) {
|
|
2096
|
+
const containerRef = useRef6(null);
|
|
2097
|
+
const widgetIdRef = useRef6(null);
|
|
2098
|
+
const onTokenRef = useRef6(onToken);
|
|
2099
|
+
onTokenRef.current = onToken;
|
|
2100
|
+
const onErrorRef = useRef6(onError);
|
|
2101
|
+
onErrorRef.current = onError;
|
|
2102
|
+
const registerResetRef = useRef6(registerReset);
|
|
2103
|
+
registerResetRef.current = registerReset;
|
|
2104
|
+
useEffect7(() => {
|
|
2105
|
+
let cancelled = false;
|
|
2106
|
+
loadTurnstileScript().then(() => {
|
|
2107
|
+
if (cancelled || !containerRef.current) return;
|
|
2108
|
+
const api = turnstileGlobal();
|
|
2109
|
+
if (!api) {
|
|
2110
|
+
onErrorRef.current?.();
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
const clearAndReset = () => {
|
|
2114
|
+
onTokenRef.current(void 0);
|
|
2115
|
+
if (widgetIdRef.current) api.reset(widgetIdRef.current);
|
|
2116
|
+
};
|
|
2117
|
+
widgetIdRef.current = api.render(containerRef.current, {
|
|
2118
|
+
sitekey: siteKey,
|
|
2119
|
+
theme: "auto",
|
|
2120
|
+
callback: (token) => onTokenRef.current(token),
|
|
2121
|
+
"error-callback": () => {
|
|
2122
|
+
onTokenRef.current(void 0);
|
|
2123
|
+
onErrorRef.current?.();
|
|
2124
|
+
},
|
|
2125
|
+
"expired-callback": clearAndReset,
|
|
2126
|
+
"timeout-callback": clearAndReset
|
|
2127
|
+
});
|
|
2128
|
+
registerResetRef.current?.(clearAndReset);
|
|
2129
|
+
}).catch(() => {
|
|
2130
|
+
if (!cancelled) onErrorRef.current?.();
|
|
2131
|
+
});
|
|
2132
|
+
return () => {
|
|
2133
|
+
cancelled = true;
|
|
2134
|
+
const api = turnstileGlobal();
|
|
2135
|
+
if (widgetIdRef.current && api) {
|
|
2136
|
+
try {
|
|
2137
|
+
api.remove(widgetIdRef.current);
|
|
2138
|
+
} catch {
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
widgetIdRef.current = null;
|
|
2142
|
+
onTokenRef.current(void 0);
|
|
2143
|
+
};
|
|
2144
|
+
}, [siteKey]);
|
|
2145
|
+
return /* @__PURE__ */ jsx6(
|
|
2146
|
+
"div",
|
|
2147
|
+
{
|
|
2148
|
+
ref: containerRef,
|
|
2149
|
+
className,
|
|
2150
|
+
"data-fillo": "turnstile"
|
|
2151
|
+
}
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
1902
2155
|
// src/FilloForm.tsx
|
|
1903
|
-
import { Fragment as
|
|
2156
|
+
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1904
2157
|
function themeProps(theme) {
|
|
1905
2158
|
const safeTheme = normalizeFormTheme(theme);
|
|
1906
2159
|
if (!safeTheme) return {};
|
|
@@ -1917,16 +2170,6 @@ function themeProps(theme) {
|
|
|
1917
2170
|
...hasColorTheme ? { "data-fillo-has-color-theme": "" } : {}
|
|
1918
2171
|
};
|
|
1919
2172
|
}
|
|
1920
|
-
function safeHttpUrl(href) {
|
|
1921
|
-
try {
|
|
1922
|
-
if (!href) return null;
|
|
1923
|
-
const url = new URL(href);
|
|
1924
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
1925
|
-
return url.toString();
|
|
1926
|
-
} catch {
|
|
1927
|
-
return null;
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
2173
|
function badgeUrl(href) {
|
|
1931
2174
|
const url = new URL(safeHttpUrl(href) ?? "https://fillo.so");
|
|
1932
2175
|
url.searchParams.set("utm_source", "form_badge");
|
|
@@ -1967,31 +2210,40 @@ function FilloForm(props) {
|
|
|
1967
2210
|
);
|
|
1968
2211
|
const [loaded, setLoaded] = useState6(null);
|
|
1969
2212
|
const [loadFailure, setLoadFailure] = useState6(null);
|
|
2213
|
+
const devEnv = useIsDevEnv();
|
|
2214
|
+
const devChrome = props.preview === true || devEnv;
|
|
2215
|
+
if (props.preview === true) warnPreviewInProduction();
|
|
1970
2216
|
const codeForm = isCodeForm2(form) ? form : null;
|
|
1971
2217
|
const onError = props.onError;
|
|
1972
2218
|
const {
|
|
1973
2219
|
formId: syncedFormId,
|
|
1974
2220
|
status: syncedStatus,
|
|
1975
2221
|
branding: syncedBranding,
|
|
2222
|
+
challenge: syncedChallenge,
|
|
1976
2223
|
error: syncError,
|
|
1977
2224
|
noticeError: syncNoticeError,
|
|
1978
2225
|
syncing: codeFormSyncing,
|
|
1979
2226
|
staged: codeChangesStaged,
|
|
2227
|
+
accepting: syncedAccepting,
|
|
2228
|
+
acceptingReason: syncedAcceptingReason,
|
|
2229
|
+
uploadsAvailable: syncedUploadsAvailable,
|
|
1980
2230
|
resolvedSchema,
|
|
1981
|
-
resolvedTheme
|
|
2231
|
+
resolvedTheme,
|
|
2232
|
+
warningCode: syncedWarningCode,
|
|
2233
|
+
warningUrl: syncedWarningUrl
|
|
1982
2234
|
} = useCodeFormSync(form, client, onError, {
|
|
1983
2235
|
// An absent client is intentional render-only use. Builder previews and an
|
|
1984
2236
|
// explicit canonical id also do not need publishable-key resolution.
|
|
1985
2237
|
allowUnsynced: Boolean(!client || props.skipValidation || formId)
|
|
1986
2238
|
});
|
|
1987
|
-
const renderResolvedSnapshot = Boolean(resolvedSchema && !
|
|
2239
|
+
const renderResolvedSnapshot = Boolean(resolvedSchema && !devChrome);
|
|
1988
2240
|
const inlineSchema = codeForm ? renderResolvedSnapshot ? resolvedSchema : codeForm.schema : form;
|
|
1989
|
-
const onErrorRef =
|
|
1990
|
-
|
|
2241
|
+
const onErrorRef = useRef7(onError);
|
|
2242
|
+
useEffect8(() => {
|
|
1991
2243
|
onErrorRef.current = onError;
|
|
1992
2244
|
});
|
|
1993
2245
|
const needsFetch = !inlineSchema && Boolean(client && formId);
|
|
1994
|
-
|
|
2246
|
+
useEffect8(() => {
|
|
1995
2247
|
if (!needsFetch || !client || !formId) {
|
|
1996
2248
|
setLoaded(null);
|
|
1997
2249
|
setLoadFailure(null);
|
|
@@ -2007,7 +2259,11 @@ function FilloForm(props) {
|
|
|
2007
2259
|
schema: published.schema,
|
|
2008
2260
|
theme: published.theme,
|
|
2009
2261
|
closed: Boolean(published.closed),
|
|
2010
|
-
|
|
2262
|
+
accepting: published.accepting,
|
|
2263
|
+
acceptingReason: published.acceptingReason,
|
|
2264
|
+
uploadsAvailable: published.uploadsAvailable,
|
|
2265
|
+
branding: published.branding ?? null,
|
|
2266
|
+
challenge: published.challenge ?? null
|
|
2011
2267
|
});
|
|
2012
2268
|
}).catch((err) => {
|
|
2013
2269
|
if (cancelled) return;
|
|
@@ -2029,25 +2285,52 @@ function FilloForm(props) {
|
|
|
2029
2285
|
const theme = props.appearance?.theme ?? props.theme ?? resolvedCodeTheme ?? currentLoaded?.theme ?? void 0;
|
|
2030
2286
|
if (normalized && !normalized.ok) {
|
|
2031
2287
|
const error = new FilloError2(`This form could not be rendered: ${normalized.error}`, 422);
|
|
2032
|
-
if (props.renderError) return /* @__PURE__ */
|
|
2033
|
-
return /* @__PURE__ */
|
|
2288
|
+
if (props.renderError) return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(error) });
|
|
2289
|
+
return /* @__PURE__ */ jsx7("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: strings.renderFailed });
|
|
2034
2290
|
}
|
|
2035
2291
|
if (loadError) {
|
|
2036
2292
|
if (props.renderError) {
|
|
2037
|
-
return /* @__PURE__ */
|
|
2293
|
+
return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(loadError) });
|
|
2038
2294
|
}
|
|
2039
2295
|
const message = loadError.status === 404 ? strings.loadFailedNotFound : loadError.status === 0 ? strings.loadFailedNetwork : strings.loadFailed;
|
|
2040
|
-
return /* @__PURE__ */
|
|
2296
|
+
return /* @__PURE__ */ jsx7("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: message });
|
|
2297
|
+
}
|
|
2298
|
+
if (currentLoaded?.accepting === false && schema) {
|
|
2299
|
+
const variant = overlayVariant(currentLoaded.acceptingReason);
|
|
2300
|
+
if (props.renderError) {
|
|
2301
|
+
const error = variant === "closed" ? new FilloError2(
|
|
2302
|
+
"This form is no longer accepting responses.",
|
|
2303
|
+
403,
|
|
2304
|
+
void 0,
|
|
2305
|
+
currentLoaded.acceptingReason
|
|
2306
|
+
) : new FilloError2(
|
|
2307
|
+
"This form isn't accepting responses yet.",
|
|
2308
|
+
403,
|
|
2309
|
+
void 0,
|
|
2310
|
+
currentLoaded.acceptingReason
|
|
2311
|
+
);
|
|
2312
|
+
return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(error) });
|
|
2313
|
+
}
|
|
2314
|
+
return /* @__PURE__ */ jsx7(
|
|
2315
|
+
NotOpenOverlay,
|
|
2316
|
+
{
|
|
2317
|
+
...props,
|
|
2318
|
+
variant,
|
|
2319
|
+
schema,
|
|
2320
|
+
theme,
|
|
2321
|
+
branding: currentLoaded.branding
|
|
2322
|
+
}
|
|
2323
|
+
);
|
|
2041
2324
|
}
|
|
2042
2325
|
if (currentLoaded?.closed) {
|
|
2043
|
-
return /* @__PURE__ */
|
|
2326
|
+
return /* @__PURE__ */ jsx7("div", { className: `fillo-form fillo-form--closed ${props.className ?? ""}`, ...themeProps(theme), children: /* @__PURE__ */ jsx7("p", { className: "fillo-closed", children: strings.closed }) });
|
|
2044
2327
|
}
|
|
2045
2328
|
const canonicalSyncError = codeForm && syncError && !props.skipValidation ? syncError : null;
|
|
2046
2329
|
if (canonicalSyncError && props.renderError) {
|
|
2047
|
-
return /* @__PURE__ */
|
|
2330
|
+
return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(canonicalSyncError) });
|
|
2048
2331
|
}
|
|
2049
|
-
if (canonicalSyncError && !
|
|
2050
|
-
return /* @__PURE__ */
|
|
2332
|
+
if (canonicalSyncError && !devChrome) {
|
|
2333
|
+
return /* @__PURE__ */ jsx7(
|
|
2051
2334
|
"div",
|
|
2052
2335
|
{
|
|
2053
2336
|
className: `fillo-form fillo-form--error ${props.className ?? ""}`,
|
|
@@ -2060,30 +2343,50 @@ function FilloForm(props) {
|
|
|
2060
2343
|
);
|
|
2061
2344
|
}
|
|
2062
2345
|
const notPublished = Boolean(codeForm) && syncedStatus === "draft" && !props.skipValidation;
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2346
|
+
const syncNotAccepting = Boolean(codeForm) && !props.skipValidation && syncedStatus === "published" && syncedAccepting === false;
|
|
2347
|
+
if ((notPublished || syncNotAccepting) && !devChrome) {
|
|
2348
|
+
const variant = notPublished ? "notOpen" : overlayVariant(syncedAcceptingReason);
|
|
2349
|
+
const error = variant === "closed" ? new FilloError2(
|
|
2350
|
+
"This form is no longer accepting responses.",
|
|
2351
|
+
403,
|
|
2352
|
+
void 0,
|
|
2353
|
+
syncedAcceptingReason
|
|
2354
|
+
) : new FilloError2("This form isn't published yet.", 403);
|
|
2355
|
+
if (props.renderError) return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(error) });
|
|
2356
|
+
if (schema) {
|
|
2357
|
+
return /* @__PURE__ */ jsx7(
|
|
2358
|
+
NotOpenOverlay,
|
|
2359
|
+
{
|
|
2360
|
+
...props,
|
|
2361
|
+
variant,
|
|
2362
|
+
schema,
|
|
2363
|
+
theme,
|
|
2364
|
+
branding: syncedBranding
|
|
2365
|
+
}
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
return /* @__PURE__ */ jsx7("div", { className: `fillo-form fillo-form--closed ${props.className ?? ""}`, ...themeProps(theme), children: /* @__PURE__ */ jsx7("p", { className: "fillo-closed", children: variant === "closed" ? strings.closed : strings.notLive }) });
|
|
2067
2369
|
}
|
|
2068
2370
|
const awaitingCanonical = Boolean(codeForm && codeFormSyncing && !props.skipValidation);
|
|
2069
|
-
if (!
|
|
2070
|
-
return /* @__PURE__ */
|
|
2371
|
+
if (!devChrome && awaitingCanonical || !schema) {
|
|
2372
|
+
return /* @__PURE__ */ jsxs6(
|
|
2071
2373
|
"div",
|
|
2072
2374
|
{
|
|
2073
2375
|
className: `fillo-form fillo-form--loading ${props.className ?? ""}`,
|
|
2074
2376
|
"aria-busy": "true",
|
|
2075
2377
|
...themeProps(theme),
|
|
2076
2378
|
children: [
|
|
2077
|
-
/* @__PURE__ */
|
|
2078
|
-
/* @__PURE__ */
|
|
2079
|
-
/* @__PURE__ */
|
|
2379
|
+
/* @__PURE__ */ jsx7("div", { className: "fillo-skeleton" }),
|
|
2380
|
+
/* @__PURE__ */ jsx7("div", { className: "fillo-skeleton" }),
|
|
2381
|
+
/* @__PURE__ */ jsx7("div", { className: "fillo-skeleton fillo-skeleton--short" })
|
|
2080
2382
|
]
|
|
2081
2383
|
}
|
|
2082
2384
|
);
|
|
2083
2385
|
}
|
|
2084
2386
|
const branding = codeForm ? syncedBranding : currentLoaded?.branding;
|
|
2085
2387
|
const poweredBy = branding?.poweredBy ?? true;
|
|
2086
|
-
|
|
2388
|
+
const challenge = props.challenge ?? (codeForm ? syncedChallenge : currentLoaded?.challenge) ?? void 0;
|
|
2389
|
+
return /* @__PURE__ */ jsx7(
|
|
2087
2390
|
ResolvedForm,
|
|
2088
2391
|
{
|
|
2089
2392
|
...props,
|
|
@@ -2096,60 +2399,103 @@ function FilloForm(props) {
|
|
|
2096
2399
|
brandHref: branding?.href,
|
|
2097
2400
|
draftNotice: notPublished,
|
|
2098
2401
|
stagedNotice: codeChangesStaged,
|
|
2099
|
-
syncErrorNotice: syncError ?? syncNoticeError ?? void 0
|
|
2402
|
+
syncErrorNotice: syncError ?? syncNoticeError ?? void 0,
|
|
2403
|
+
uploadsAvailable: codeForm ? syncedUploadsAvailable : currentLoaded?.uploadsAvailable,
|
|
2404
|
+
syncWarningCode: codeForm ? syncedWarningCode : void 0,
|
|
2405
|
+
syncWarningUrl: syncedWarningUrl,
|
|
2406
|
+
challenge
|
|
2100
2407
|
}
|
|
2101
2408
|
);
|
|
2102
2409
|
}
|
|
2103
2410
|
function PoweredBy({ label, href }) {
|
|
2104
|
-
return /* @__PURE__ */
|
|
2105
|
-
}
|
|
2106
|
-
function isDevEnv() {
|
|
2107
|
-
return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
2411
|
+
return /* @__PURE__ */ jsx7("a", { className: "fillo-powered", href: badgeUrl(href), target: "_blank", rel: "noopener noreferrer", children: label ?? "Powered by Fillo" });
|
|
2108
2412
|
}
|
|
2109
|
-
function
|
|
2110
|
-
return
|
|
2111
|
-
/* @__PURE__ */ jsx5("strong", { children: "No Fillo client connected." }),
|
|
2112
|
-
" This form is render-only and won\u2019t save responses. Pass a ",
|
|
2113
|
-
/* @__PURE__ */ jsx5("code", { children: "client" }),
|
|
2114
|
-
" to collect them in Fillo, or forward them with webhooks. (This notice only appears in development.)"
|
|
2115
|
-
] });
|
|
2116
|
-
}
|
|
2117
|
-
function DevDraftNotice() {
|
|
2118
|
-
return /* @__PURE__ */ jsxs5("div", { className: "fillo-devwarning", role: "alert", children: [
|
|
2119
|
-
/* @__PURE__ */ jsx5("strong", { children: "Draft form." }),
|
|
2120
|
-
" It renders here, but it won\u2019t accept responses until you publish it in your Fillo dashboard. (This notice only appears in development.)"
|
|
2121
|
-
] });
|
|
2413
|
+
function overlayVariant(reason) {
|
|
2414
|
+
return reason === "expired" || reason === "capped" || reason === "storage_full" ? "closed" : "notOpen";
|
|
2122
2415
|
}
|
|
2123
|
-
function
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2416
|
+
function NotOpenOverlay(props) {
|
|
2417
|
+
const strings = resolveStrings(props.strings);
|
|
2418
|
+
const closedFlavor = props.variant === "closed";
|
|
2419
|
+
return /* @__PURE__ */ jsxs6(
|
|
2420
|
+
"div",
|
|
2421
|
+
{
|
|
2422
|
+
className: `fillo-form fillo-form--not-open ${props.className ?? ""}`,
|
|
2423
|
+
"data-fillo": "root",
|
|
2424
|
+
"data-state": "closed",
|
|
2425
|
+
...themeProps(props.theme),
|
|
2426
|
+
children: [
|
|
2427
|
+
/* @__PURE__ */ jsx7(
|
|
2428
|
+
"div",
|
|
2429
|
+
{
|
|
2430
|
+
className: "fillo-not-open-preview",
|
|
2431
|
+
"aria-hidden": "true",
|
|
2432
|
+
ref: (node) => node?.setAttribute("inert", ""),
|
|
2433
|
+
children: /* @__PURE__ */ jsx7(
|
|
2434
|
+
ResolvedForm,
|
|
2435
|
+
{
|
|
2436
|
+
schema: props.schema,
|
|
2437
|
+
theme: props.theme,
|
|
2438
|
+
appearance: props.appearance,
|
|
2439
|
+
strings: props.strings,
|
|
2440
|
+
components: props.components,
|
|
2441
|
+
customComponents: props.customComponents,
|
|
2442
|
+
initialData: props.initialData,
|
|
2443
|
+
showTitle: props.showTitle,
|
|
2444
|
+
displayOnly: true
|
|
2445
|
+
}
|
|
2446
|
+
)
|
|
2447
|
+
}
|
|
2448
|
+
),
|
|
2449
|
+
/* @__PURE__ */ jsxs6("div", { className: "fillo-not-open-card", "data-fillo": "not-open-card", role: "status", children: [
|
|
2450
|
+
/* @__PURE__ */ jsx7("h2", { className: "fillo-not-open-title", children: closedFlavor ? strings.closedTitle : strings.notOpenTitle }),
|
|
2451
|
+
/* @__PURE__ */ jsx7("p", { className: "fillo-not-open-body", children: closedFlavor ? strings.closed : strings.notOpenBody })
|
|
2452
|
+
] }),
|
|
2453
|
+
(props.branding?.poweredBy ?? true) && /* @__PURE__ */ jsx7(PoweredBy, { label: props.branding?.label, href: props.branding?.href })
|
|
2454
|
+
]
|
|
2455
|
+
}
|
|
2456
|
+
);
|
|
2128
2457
|
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
error.code ? ` (${error.code})` : "",
|
|
2134
|
-
"."
|
|
2135
|
-
] }),
|
|
2136
|
-
" ",
|
|
2137
|
-
error.message,
|
|
2138
|
-
" The safe form remains available for development."
|
|
2139
|
-
] });
|
|
2458
|
+
var noopSubscribe = () => () => {
|
|
2459
|
+
};
|
|
2460
|
+
function useIsDevEnv() {
|
|
2461
|
+
return useSyncExternalStore2(noopSubscribe, isLikelyDevEnv, isBuildTimeDevEnv2);
|
|
2140
2462
|
}
|
|
2141
2463
|
function ResolvedForm(props) {
|
|
2142
2464
|
const { schema, theme } = props;
|
|
2465
|
+
const displayOnly = props.displayOnly === true;
|
|
2143
2466
|
const instanceId = useId2().replace(/:/g, "");
|
|
2144
|
-
const
|
|
2467
|
+
const devEnv = useIsDevEnv();
|
|
2468
|
+
const devChrome = !displayOnly && (props.preview === true || devEnv);
|
|
2469
|
+
const hpRef = useRef7(null);
|
|
2145
2470
|
const codeForm = isCodeForm2(props.form) ? props.form : null;
|
|
2146
2471
|
const client = props.client;
|
|
2472
|
+
const challenge = props.challenge;
|
|
2473
|
+
const challengeRequired = Boolean(challenge);
|
|
2474
|
+
const challengeTokenRef = useRef7(void 0);
|
|
2475
|
+
const [resyncWarning, setResyncWarning] = useState6(null);
|
|
2476
|
+
const [resolutionFailureCode, setResolutionFailureCode] = useState6(null);
|
|
2477
|
+
const [challengeToken, setChallengeToken] = useState6(void 0);
|
|
2478
|
+
const [challengeError, setChallengeError] = useState6(false);
|
|
2479
|
+
const resetChallengeRef = useRef7(void 0);
|
|
2480
|
+
const handleChallengeToken = useCallback((token) => {
|
|
2481
|
+
challengeTokenRef.current = token;
|
|
2482
|
+
setChallengeToken(token);
|
|
2483
|
+
if (token) setChallengeError(false);
|
|
2484
|
+
}, []);
|
|
2485
|
+
const registerChallengeReset = useCallback((reset) => {
|
|
2486
|
+
resetChallengeRef.current = reset;
|
|
2487
|
+
}, []);
|
|
2147
2488
|
const resolveFormId = useMemo5(
|
|
2148
2489
|
() => codeForm && client?.key ? (
|
|
2149
2490
|
// bypassCache: a stale cached formId must not be trusted at submit time.
|
|
2150
2491
|
async () => {
|
|
2151
2492
|
try {
|
|
2152
2493
|
const result = await syncCodeForm2(client, codeForm, { bypassCache: true });
|
|
2494
|
+
setResyncWarning({
|
|
2495
|
+
code: result.warningCode,
|
|
2496
|
+
url: result.warningUrl,
|
|
2497
|
+
uploadsAvailable: result.uploadsAvailable
|
|
2498
|
+
});
|
|
2153
2499
|
if (result.status === "draft") {
|
|
2154
2500
|
throw new FilloError2(
|
|
2155
2501
|
"This form is no longer published. Submission was stopped before sending answers.",
|
|
@@ -2158,6 +2504,14 @@ function ResolvedForm(props) {
|
|
|
2158
2504
|
"form_not_published"
|
|
2159
2505
|
);
|
|
2160
2506
|
}
|
|
2507
|
+
if (result.accepting === false) {
|
|
2508
|
+
throw new FilloError2(
|
|
2509
|
+
"This form is no longer accepting responses. Submission was stopped before sending answers.",
|
|
2510
|
+
403,
|
|
2511
|
+
void 0,
|
|
2512
|
+
result.acceptingReason ?? "form_not_accepting"
|
|
2513
|
+
);
|
|
2514
|
+
}
|
|
2161
2515
|
const authoritative = normalizeFormSchema(result.resolvedSchema ?? codeForm.schema);
|
|
2162
2516
|
if (!authoritative.ok) {
|
|
2163
2517
|
throw new FilloError2(
|
|
@@ -2187,9 +2541,11 @@ function ResolvedForm(props) {
|
|
|
2187
2541
|
);
|
|
2188
2542
|
props.onError?.(notice);
|
|
2189
2543
|
}
|
|
2544
|
+
setResolutionFailureCode(null);
|
|
2190
2545
|
return result.formId;
|
|
2191
2546
|
} catch (error) {
|
|
2192
2547
|
const failure = isFilloError2(error) ? error : new FilloError2(error instanceof Error ? error.message : String(error), 0);
|
|
2548
|
+
setResolutionFailureCode(failure.code ?? "sync_failed");
|
|
2193
2549
|
console.warn(
|
|
2194
2550
|
`[fillo] form sync failed${failure.code ? ` (${failure.code})` : ""}: ${failure.message}`
|
|
2195
2551
|
);
|
|
@@ -2213,25 +2569,46 @@ function ResolvedForm(props) {
|
|
|
2213
2569
|
// "default", never the bare-engine "headless".
|
|
2214
2570
|
surface: "default",
|
|
2215
2571
|
resolveFormId,
|
|
2216
|
-
|
|
2572
|
+
// Dev chrome gets the developer-grade resolution failure (message + code)
|
|
2573
|
+
// instead of the respondent-safe "unavailable" fallback.
|
|
2574
|
+
verboseResolutionErrors: devChrome,
|
|
2575
|
+
respondent: props.respondent,
|
|
2576
|
+
challengeRequired,
|
|
2577
|
+
getChallengeToken: () => challengeTokenRef.current,
|
|
2578
|
+
onChallengeFailed: () => {
|
|
2579
|
+
handleChallengeToken(void 0);
|
|
2580
|
+
resetChallengeRef.current?.();
|
|
2581
|
+
}
|
|
2217
2582
|
});
|
|
2218
|
-
useDraftFlush(api);
|
|
2583
|
+
useDraftFlush(api, !displayOnly);
|
|
2219
2584
|
const themed = useMemo5(() => themeProps(theme), [theme]);
|
|
2585
|
+
const warningCode = resyncWarning ? resyncWarning.code : props.syncWarningCode;
|
|
2586
|
+
const warningUrl = resyncWarning ? resyncWarning.url : props.syncWarningUrl;
|
|
2587
|
+
const uploadsAvailable = resyncWarning ? resyncWarning.uploadsAvailable : props.uploadsAvailable;
|
|
2588
|
+
const chromeValue = useMemo5(
|
|
2589
|
+
() => ({
|
|
2590
|
+
devChrome,
|
|
2591
|
+
uploadsAvailable,
|
|
2592
|
+
warningCode,
|
|
2593
|
+
warningUrl
|
|
2594
|
+
}),
|
|
2595
|
+
[devChrome, uploadsAvailable, warningCode, warningUrl]
|
|
2596
|
+
);
|
|
2220
2597
|
const settings = schema.settings;
|
|
2221
2598
|
const submitted = api.status === "submitted";
|
|
2222
2599
|
const liveSubmit = submitted && !api.restoredSubmission;
|
|
2223
|
-
|
|
2600
|
+
useEffect8(() => {
|
|
2224
2601
|
if (!liveSubmit || !settings.redirectUrl || typeof window === "undefined") return;
|
|
2225
2602
|
const url = safeHttpUrl(settings.redirectUrl);
|
|
2226
2603
|
if (url) window.location.assign(url);
|
|
2227
2604
|
}, [liveSubmit, settings.redirectUrl]);
|
|
2228
|
-
const successRef =
|
|
2229
|
-
|
|
2605
|
+
const successRef = useRef7(null);
|
|
2606
|
+
useEffect8(() => {
|
|
2230
2607
|
if (liveSubmit) successRef.current?.focus();
|
|
2231
2608
|
}, [liveSubmit]);
|
|
2232
|
-
const blocksRef =
|
|
2233
|
-
const prevPageIndex =
|
|
2234
|
-
|
|
2609
|
+
const blocksRef = useRef7(null);
|
|
2610
|
+
const prevPageIndex = useRef7(api.pageIndex);
|
|
2611
|
+
useEffect8(() => {
|
|
2235
2612
|
if (prevPageIndex.current === api.pageIndex) return;
|
|
2236
2613
|
prevPageIndex.current = api.pageIndex;
|
|
2237
2614
|
blocksRef.current?.focus();
|
|
@@ -2239,7 +2616,7 @@ function ResolvedForm(props) {
|
|
|
2239
2616
|
const appearance = props.appearance;
|
|
2240
2617
|
const strings = resolveStrings(props.strings);
|
|
2241
2618
|
if (api.status === "submitted") {
|
|
2242
|
-
return /* @__PURE__ */
|
|
2619
|
+
return /* @__PURE__ */ jsxs6(
|
|
2243
2620
|
"div",
|
|
2244
2621
|
{
|
|
2245
2622
|
ref: successRef,
|
|
@@ -2255,34 +2632,190 @@ function ResolvedForm(props) {
|
|
|
2255
2632
|
"data-state": "submitted",
|
|
2256
2633
|
...themed,
|
|
2257
2634
|
children: [
|
|
2258
|
-
props.renderSuccess ? props.renderSuccess() : /* @__PURE__ */
|
|
2635
|
+
props.renderSuccess ? props.renderSuccess() : /* @__PURE__ */ jsxs6(
|
|
2259
2636
|
"div",
|
|
2260
2637
|
{
|
|
2261
2638
|
className: slotClass3("fillo-success", appearance, { slot: "success", status: "submitted" }),
|
|
2262
2639
|
"data-fillo": "success",
|
|
2263
2640
|
children: [
|
|
2264
|
-
/* @__PURE__ */
|
|
2265
|
-
/* @__PURE__ */
|
|
2266
|
-
/* @__PURE__ */
|
|
2641
|
+
/* @__PURE__ */ jsx7("div", { className: "fillo-success-mark", "aria-hidden": "true", children: "\u2713" }),
|
|
2642
|
+
/* @__PURE__ */ jsx7("h2", { className: "fillo-success-title", children: settings.successTitle ?? strings.successTitle }),
|
|
2643
|
+
/* @__PURE__ */ jsx7("p", { className: "fillo-success-message", children: api.duplicateSubmission ? strings.alreadyAnswered : settings.successMessage ?? strings.successMessage })
|
|
2267
2644
|
]
|
|
2268
2645
|
}
|
|
2269
2646
|
),
|
|
2270
|
-
props.poweredBy && /* @__PURE__ */
|
|
2647
|
+
props.poweredBy && /* @__PURE__ */ jsx7(PoweredBy, { label: props.brandLabel, href: props.brandHref })
|
|
2271
2648
|
]
|
|
2272
2649
|
}
|
|
2273
2650
|
);
|
|
2274
2651
|
}
|
|
2275
2652
|
const multiPage = api.pageCount > 1;
|
|
2276
2653
|
const autoSubmit = settings.submitMode === "auto";
|
|
2277
|
-
const
|
|
2654
|
+
const showChallenge = challengeRequired && api.isLastPage;
|
|
2655
|
+
const showPrimaryButton = !autoSubmit || !api.isLastPage || needsExplicitSubmit(reachableFields(api.form, api.data)) || // A challenge needs an explicit Submit even on an otherwise button-less
|
|
2656
|
+
// auto-submit form — you can't one-tap past a human check.
|
|
2657
|
+
showChallenge;
|
|
2278
2658
|
const showFooter = showPrimaryButton || multiPage && !api.isFirstPage;
|
|
2279
|
-
|
|
2659
|
+
const progressSeq = reachablePageSequence(api.form, api.data);
|
|
2660
|
+
const progressTotal = Math.max(progressSeq.length, 1);
|
|
2661
|
+
const progressPos = progressSeq.indexOf(api.page.id);
|
|
2662
|
+
const progressStep = progressPos >= 0 ? progressPos + 1 : Math.min(api.pageIndex + 1, progressTotal);
|
|
2663
|
+
const submitFixUrl = devChrome && resolutionFailureCode === "form_not_published" && warningCode === "storage_required" ? safeHttpUrl(warningUrl) : null;
|
|
2664
|
+
const rootClass = slotClass3(`fillo-form ${props.className ?? ""}`, appearance, {
|
|
2665
|
+
slot: "root",
|
|
2666
|
+
status: api.status
|
|
2667
|
+
});
|
|
2668
|
+
const formBody = /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
2669
|
+
devChrome && /* @__PURE__ */ jsx7(
|
|
2670
|
+
DevChrome,
|
|
2671
|
+
{
|
|
2672
|
+
preview: props.preview,
|
|
2673
|
+
devNotices: props.devNotices,
|
|
2674
|
+
syncError: props.syncErrorNotice,
|
|
2675
|
+
staged: props.stagedNotice,
|
|
2676
|
+
draft: props.draftNotice,
|
|
2677
|
+
warningUrl: props.syncWarningUrl,
|
|
2678
|
+
noClient: !props.client && !props.skipValidation
|
|
2679
|
+
}
|
|
2680
|
+
),
|
|
2681
|
+
(api.resumedDraft || api.editingPrevious) && /* @__PURE__ */ jsxs6(
|
|
2682
|
+
"div",
|
|
2683
|
+
{
|
|
2684
|
+
className: slotClass3("fillo-resume", appearance, { slot: "resume" }),
|
|
2685
|
+
"data-fillo": "resume",
|
|
2686
|
+
role: "status",
|
|
2687
|
+
children: [
|
|
2688
|
+
/* @__PURE__ */ jsx7("span", { className: "fillo-resume-text", children: api.editingPrevious ? strings.editNotice : strings.resumeNotice }),
|
|
2689
|
+
/* @__PURE__ */ jsx7("button", { type: "button", className: "fillo-resume-clear", onClick: api.resetDraft, children: strings.resumeStartOver })
|
|
2690
|
+
]
|
|
2691
|
+
}
|
|
2692
|
+
),
|
|
2693
|
+
api.resumeLinkFailed && !api.resumedDraft && !api.editingPrevious && /* @__PURE__ */ jsx7(
|
|
2694
|
+
"div",
|
|
2695
|
+
{
|
|
2696
|
+
className: slotClass3("fillo-resume fillo-resume--expired", appearance, { slot: "resume" }),
|
|
2697
|
+
"data-fillo": "resume",
|
|
2698
|
+
role: "status",
|
|
2699
|
+
children: /* @__PURE__ */ jsx7("span", { className: "fillo-resume-text", children: strings.resumeLinkExpired })
|
|
2700
|
+
}
|
|
2701
|
+
),
|
|
2702
|
+
multiPage && settings.showProgress !== false && /* @__PURE__ */ jsx7(
|
|
2703
|
+
"div",
|
|
2704
|
+
{
|
|
2705
|
+
className: slotClass3("fillo-progress-track", appearance, { slot: "progress" }),
|
|
2706
|
+
"data-fillo": "progress",
|
|
2707
|
+
role: "progressbar",
|
|
2708
|
+
"aria-label": schema.title || "Form progress",
|
|
2709
|
+
"aria-valuemin": 1,
|
|
2710
|
+
"aria-valuemax": progressTotal,
|
|
2711
|
+
"aria-valuenow": progressStep,
|
|
2712
|
+
children: /* @__PURE__ */ jsx7(
|
|
2713
|
+
"div",
|
|
2714
|
+
{
|
|
2715
|
+
className: slotClass3("fillo-progress-fill", appearance, { slot: "progressFill" }),
|
|
2716
|
+
"data-fillo": "progressFill",
|
|
2717
|
+
style: { "--fillo-progress-value": `${progressStep / progressTotal * 100}%` }
|
|
2718
|
+
}
|
|
2719
|
+
)
|
|
2720
|
+
}
|
|
2721
|
+
),
|
|
2722
|
+
api.pageIndex === 0 && props.showTitle !== false && /* @__PURE__ */ jsxs6("header", { className: slotClass3("fillo-header", appearance, { slot: "header" }), "data-fillo": "header", children: [
|
|
2723
|
+
schema.title && /* @__PURE__ */ jsx7("h1", { className: slotClass3("fillo-title", appearance, { slot: "title" }), "data-fillo": "title", children: schema.title }),
|
|
2724
|
+
schema.description && /* @__PURE__ */ jsx7(
|
|
2725
|
+
"p",
|
|
2726
|
+
{
|
|
2727
|
+
className: slotClass3("fillo-form-description", appearance, { slot: "description" }),
|
|
2728
|
+
"data-fillo": "description",
|
|
2729
|
+
children: schema.description
|
|
2730
|
+
}
|
|
2731
|
+
)
|
|
2732
|
+
] }),
|
|
2733
|
+
multiPage && api.page.title && api.pageIndex > 0 && /* @__PURE__ */ jsx7("h2", { className: slotClass3("fillo-page-title", appearance, { slot: "pageTitle" }), "data-fillo": "pageTitle", children: api.page.title }),
|
|
2734
|
+
/* @__PURE__ */ jsx7("div", { className: slotClass3("fillo-blocks", appearance, { slot: "blocks" }), "data-fillo": "blocks", ref: blocksRef, tabIndex: -1, children: api.blocks.map((block) => /* @__PURE__ */ jsx7(
|
|
2735
|
+
BlockRenderer,
|
|
2736
|
+
{
|
|
2737
|
+
block,
|
|
2738
|
+
api,
|
|
2739
|
+
components: props.components,
|
|
2740
|
+
customComponents: props.customComponents
|
|
2741
|
+
},
|
|
2742
|
+
block.id
|
|
2743
|
+
)) }),
|
|
2744
|
+
!displayOnly && /* @__PURE__ */ jsx7(
|
|
2745
|
+
"input",
|
|
2746
|
+
{
|
|
2747
|
+
type: "text",
|
|
2748
|
+
name: "fillo_hp_field",
|
|
2749
|
+
className: "fillo-hp",
|
|
2750
|
+
tabIndex: -1,
|
|
2751
|
+
autoComplete: "off",
|
|
2752
|
+
"aria-hidden": "true",
|
|
2753
|
+
defaultValue: "",
|
|
2754
|
+
style: HP_STYLE,
|
|
2755
|
+
ref: (element) => {
|
|
2756
|
+
hpRef.current = element;
|
|
2757
|
+
assertHpHidden(element);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
),
|
|
2761
|
+
challenge && showChallenge && /* @__PURE__ */ jsxs6("div", { className: "fillo-turnstile-slot", "data-fillo": "turnstile-slot", children: [
|
|
2762
|
+
/* @__PURE__ */ jsx7(
|
|
2763
|
+
TurnstileWidget,
|
|
2764
|
+
{
|
|
2765
|
+
siteKey: challenge.siteKey,
|
|
2766
|
+
className: slotClass3("fillo-turnstile", appearance, { slot: "turnstile" }),
|
|
2767
|
+
onToken: handleChallengeToken,
|
|
2768
|
+
onError: () => setChallengeError(true),
|
|
2769
|
+
registerReset: registerChallengeReset
|
|
2770
|
+
}
|
|
2771
|
+
),
|
|
2772
|
+
challengeError && /* @__PURE__ */ jsx7("p", { className: "fillo-turnstile-error", role: "alert", children: strings.challengeUnavailable })
|
|
2773
|
+
] }),
|
|
2774
|
+
(api.status === "error" || api.submitError) && /* @__PURE__ */ jsxs6("p", { className: "fillo-submit-error", role: "alert", children: [
|
|
2775
|
+
api.submitError ?? strings.submitFailed,
|
|
2776
|
+
submitFixUrl && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
2777
|
+
" ",
|
|
2778
|
+
"Connect storage:",
|
|
2779
|
+
" ",
|
|
2780
|
+
/* @__PURE__ */ jsx7("a", { href: submitFixUrl, target: "_blank", rel: "noopener noreferrer", children: submitFixUrl })
|
|
2781
|
+
] })
|
|
2782
|
+
] }),
|
|
2783
|
+
showFooter && /* @__PURE__ */ jsxs6("footer", { className: slotClass3("fillo-footer", appearance, { slot: "footer" }), "data-fillo": "footer", children: [
|
|
2784
|
+
multiPage && !api.isFirstPage && /* @__PURE__ */ jsx7(
|
|
2785
|
+
"button",
|
|
2786
|
+
{
|
|
2787
|
+
type: "button",
|
|
2788
|
+
className: slotClass3("fillo-button fillo-button--ghost", appearance, { slot: "button", variant: "ghost" }),
|
|
2789
|
+
"data-fillo": "button",
|
|
2790
|
+
onClick: api.back,
|
|
2791
|
+
children: strings.back
|
|
2792
|
+
}
|
|
2793
|
+
),
|
|
2794
|
+
showPrimaryButton && /* @__PURE__ */ jsx7(
|
|
2795
|
+
"button",
|
|
2796
|
+
{
|
|
2797
|
+
type: "submit",
|
|
2798
|
+
className: slotClass3("fillo-button fillo-button--primary", appearance, { slot: "button", variant: "primary" }),
|
|
2799
|
+
"data-fillo": "button",
|
|
2800
|
+
disabled: api.status === "submitting" || api.uploading || // Wait for the human check before enabling submit (never
|
|
2801
|
+
// "Next"). The server rejects a tokenless submit regardless —
|
|
2802
|
+
// this is UX only.
|
|
2803
|
+
showChallenge && !challengeToken,
|
|
2804
|
+
children: api.uploading ? strings.uploading : api.status === "submitting" ? strings.submitting : api.isLastPage ? settings.submitLabel ?? strings.submit : strings.next
|
|
2805
|
+
}
|
|
2806
|
+
)
|
|
2807
|
+
] }),
|
|
2808
|
+
props.poweredBy && /* @__PURE__ */ jsx7(PoweredBy, { label: props.brandLabel, href: props.brandHref })
|
|
2809
|
+
] });
|
|
2810
|
+
return /* @__PURE__ */ jsx7(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx7(FilloChromeContext.Provider, { value: chromeValue, children: /* @__PURE__ */ jsx7(FilloStringsContext.Provider, { value: strings, children: /* @__PURE__ */ jsx7(FilloAppearanceContext.Provider, { value: appearance, children: /* @__PURE__ */ jsx7(FilloInstanceIdContext.Provider, { value: instanceId, children: displayOnly ? (
|
|
2811
|
+
// Display-only preview: a plain div (no form element to submit) with
|
|
2812
|
+
// every control inside a natively disabled fieldset — unfocusable
|
|
2813
|
+
// and unfillable even without the stylesheet or inert support.
|
|
2814
|
+
/* @__PURE__ */ jsx7("div", { className: rootClass, "data-fillo": "preview-form", "data-state": "preview", ...themed, children: /* @__PURE__ */ jsx7("fieldset", { disabled: true, className: "fillo-not-open-fields", children: formBody }) })
|
|
2815
|
+
) : /* @__PURE__ */ jsx7(
|
|
2280
2816
|
"form",
|
|
2281
2817
|
{
|
|
2282
|
-
className:
|
|
2283
|
-
slot: "root",
|
|
2284
|
-
status: api.status
|
|
2285
|
-
}),
|
|
2818
|
+
className: rootClass,
|
|
2286
2819
|
"data-fillo": "root",
|
|
2287
2820
|
"data-state": api.status,
|
|
2288
2821
|
"data-page": api.pageIndex + 1,
|
|
@@ -2300,136 +2833,28 @@ function ResolvedForm(props) {
|
|
|
2300
2833
|
focusFirstInvalid(formEl);
|
|
2301
2834
|
}
|
|
2302
2835
|
},
|
|
2303
|
-
children:
|
|
2304
|
-
!props.client && !props.skipValidation && isDevEnv() && /* @__PURE__ */ jsx5(DevNoClientNotice, {}),
|
|
2305
|
-
props.draftNotice && isDevEnv() && /* @__PURE__ */ jsx5(DevDraftNotice, {}),
|
|
2306
|
-
props.stagedNotice && isDevEnv() && /* @__PURE__ */ jsx5(DevStagedNotice, {}),
|
|
2307
|
-
props.syncErrorNotice && isDevEnv() && /* @__PURE__ */ jsx5(DevSyncErrorNotice, { error: props.syncErrorNotice }),
|
|
2308
|
-
(api.resumedDraft || api.editingPrevious) && /* @__PURE__ */ jsxs5(
|
|
2309
|
-
"div",
|
|
2310
|
-
{
|
|
2311
|
-
className: slotClass3("fillo-resume", appearance, { slot: "resume" }),
|
|
2312
|
-
"data-fillo": "resume",
|
|
2313
|
-
role: "status",
|
|
2314
|
-
children: [
|
|
2315
|
-
/* @__PURE__ */ jsx5("span", { className: "fillo-resume-text", children: api.editingPrevious ? strings.editNotice : strings.resumeNotice }),
|
|
2316
|
-
/* @__PURE__ */ jsx5("button", { type: "button", className: "fillo-resume-clear", onClick: api.resetDraft, children: strings.resumeStartOver })
|
|
2317
|
-
]
|
|
2318
|
-
}
|
|
2319
|
-
),
|
|
2320
|
-
api.resumeLinkFailed && !api.resumedDraft && !api.editingPrevious && /* @__PURE__ */ jsx5(
|
|
2321
|
-
"div",
|
|
2322
|
-
{
|
|
2323
|
-
className: slotClass3("fillo-resume fillo-resume--expired", appearance, { slot: "resume" }),
|
|
2324
|
-
"data-fillo": "resume",
|
|
2325
|
-
role: "status",
|
|
2326
|
-
children: /* @__PURE__ */ jsx5("span", { className: "fillo-resume-text", children: strings.resumeLinkExpired })
|
|
2327
|
-
}
|
|
2328
|
-
),
|
|
2329
|
-
multiPage && settings.showProgress !== false && /* @__PURE__ */ jsx5(
|
|
2330
|
-
"div",
|
|
2331
|
-
{
|
|
2332
|
-
className: slotClass3("fillo-progress-track", appearance, { slot: "progress" }),
|
|
2333
|
-
"data-fillo": "progress",
|
|
2334
|
-
role: "progressbar",
|
|
2335
|
-
"aria-label": schema.title || "Form progress",
|
|
2336
|
-
"aria-valuemin": 1,
|
|
2337
|
-
"aria-valuemax": api.pageCount,
|
|
2338
|
-
"aria-valuenow": api.pageIndex + 1,
|
|
2339
|
-
children: /* @__PURE__ */ jsx5(
|
|
2340
|
-
"div",
|
|
2341
|
-
{
|
|
2342
|
-
className: slotClass3("fillo-progress-fill", appearance, { slot: "progressFill" }),
|
|
2343
|
-
"data-fillo": "progressFill",
|
|
2344
|
-
style: { "--fillo-progress-value": `${(api.pageIndex + 1) / api.pageCount * 100}%` }
|
|
2345
|
-
}
|
|
2346
|
-
)
|
|
2347
|
-
}
|
|
2348
|
-
),
|
|
2349
|
-
api.pageIndex === 0 && props.showTitle !== false && /* @__PURE__ */ jsxs5("header", { className: slotClass3("fillo-header", appearance, { slot: "header" }), "data-fillo": "header", children: [
|
|
2350
|
-
schema.title && /* @__PURE__ */ jsx5("h1", { className: slotClass3("fillo-title", appearance, { slot: "title" }), "data-fillo": "title", children: schema.title }),
|
|
2351
|
-
schema.description && /* @__PURE__ */ jsx5(
|
|
2352
|
-
"p",
|
|
2353
|
-
{
|
|
2354
|
-
className: slotClass3("fillo-form-description", appearance, { slot: "description" }),
|
|
2355
|
-
"data-fillo": "description",
|
|
2356
|
-
children: schema.description
|
|
2357
|
-
}
|
|
2358
|
-
)
|
|
2359
|
-
] }),
|
|
2360
|
-
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 }),
|
|
2361
|
-
/* @__PURE__ */ jsx5("div", { className: slotClass3("fillo-blocks", appearance, { slot: "blocks" }), "data-fillo": "blocks", ref: blocksRef, tabIndex: -1, children: api.blocks.map((block) => /* @__PURE__ */ jsx5(
|
|
2362
|
-
BlockRenderer,
|
|
2363
|
-
{
|
|
2364
|
-
block,
|
|
2365
|
-
api,
|
|
2366
|
-
components: props.components,
|
|
2367
|
-
customComponents: props.customComponents
|
|
2368
|
-
},
|
|
2369
|
-
block.id
|
|
2370
|
-
)) }),
|
|
2371
|
-
/* @__PURE__ */ jsx5(
|
|
2372
|
-
"input",
|
|
2373
|
-
{
|
|
2374
|
-
type: "text",
|
|
2375
|
-
name: "fillo_hp_field",
|
|
2376
|
-
className: "fillo-hp",
|
|
2377
|
-
tabIndex: -1,
|
|
2378
|
-
autoComplete: "off",
|
|
2379
|
-
"aria-hidden": "true",
|
|
2380
|
-
defaultValue: "",
|
|
2381
|
-
style: HP_STYLE,
|
|
2382
|
-
ref: (element) => {
|
|
2383
|
-
hpRef.current = element;
|
|
2384
|
-
assertHpHidden(element);
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
),
|
|
2388
|
-
(api.status === "error" || api.submitError) && /* @__PURE__ */ jsx5("p", { className: "fillo-submit-error", role: "alert", children: api.submitError ?? strings.submitFailed }),
|
|
2389
|
-
showFooter && /* @__PURE__ */ jsxs5("footer", { className: slotClass3("fillo-footer", appearance, { slot: "footer" }), "data-fillo": "footer", children: [
|
|
2390
|
-
multiPage && !api.isFirstPage && /* @__PURE__ */ jsx5(
|
|
2391
|
-
"button",
|
|
2392
|
-
{
|
|
2393
|
-
type: "button",
|
|
2394
|
-
className: slotClass3("fillo-button fillo-button--ghost", appearance, { slot: "button", variant: "ghost" }),
|
|
2395
|
-
"data-fillo": "button",
|
|
2396
|
-
onClick: api.back,
|
|
2397
|
-
children: strings.back
|
|
2398
|
-
}
|
|
2399
|
-
),
|
|
2400
|
-
showPrimaryButton && /* @__PURE__ */ jsx5(
|
|
2401
|
-
"button",
|
|
2402
|
-
{
|
|
2403
|
-
type: "submit",
|
|
2404
|
-
className: slotClass3("fillo-button fillo-button--primary", appearance, { slot: "button", variant: "primary" }),
|
|
2405
|
-
"data-fillo": "button",
|
|
2406
|
-
disabled: api.status === "submitting" || api.uploading,
|
|
2407
|
-
children: api.uploading ? strings.uploading : api.status === "submitting" ? strings.submitting : api.isLastPage ? settings.submitLabel ?? strings.submit : strings.next
|
|
2408
|
-
}
|
|
2409
|
-
)
|
|
2410
|
-
] }),
|
|
2411
|
-
props.poweredBy && /* @__PURE__ */ jsx5(PoweredBy, { label: props.brandLabel, href: props.brandHref })
|
|
2412
|
-
]
|
|
2836
|
+
children: formBody
|
|
2413
2837
|
}
|
|
2414
|
-
) }) }) }) });
|
|
2838
|
+
) }) }) }) }) });
|
|
2415
2839
|
}
|
|
2416
2840
|
|
|
2417
2841
|
// src/jsx.tsx
|
|
2418
|
-
import { useRef as
|
|
2842
|
+
import { useRef as useRef8 } from "react";
|
|
2419
2843
|
import {
|
|
2420
2844
|
codeFormFromJsx,
|
|
2421
2845
|
FilloJsxError,
|
|
2846
|
+
isLikelyDevEnv as isLikelyDevEnv2,
|
|
2422
2847
|
JSX_BLOCK_COMPONENTS
|
|
2423
2848
|
} from "@usefillo/core";
|
|
2424
|
-
import { jsx as
|
|
2849
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
2425
2850
|
var B = JSX_BLOCK_COMPONENTS;
|
|
2426
|
-
var
|
|
2851
|
+
var isDevEnv = () => isLikelyDevEnv2();
|
|
2427
2852
|
function JsxForm(props) {
|
|
2428
2853
|
const { id, title, description, settings, theme, client, children, ...rest } = props;
|
|
2429
2854
|
const compiled = codeFormFromJsx({ id, title, description, settings, theme }, children);
|
|
2430
2855
|
const json = JSON.stringify(compiled.schema) + JSON.stringify(compiled.theme ?? null);
|
|
2431
|
-
const memo2 =
|
|
2432
|
-
if (memo2.current && memo2.current.json !== json &&
|
|
2856
|
+
const memo2 = useRef8(null);
|
|
2857
|
+
if (memo2.current && memo2.current.json !== json && isDevEnv()) {
|
|
2433
2858
|
console.warn(
|
|
2434
2859
|
`[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.`
|
|
2435
2860
|
);
|
|
@@ -2437,7 +2862,7 @@ function JsxForm(props) {
|
|
|
2437
2862
|
if (!memo2.current || memo2.current.json !== json) {
|
|
2438
2863
|
memo2.current = { json, form: compiled };
|
|
2439
2864
|
}
|
|
2440
|
-
return /* @__PURE__ */
|
|
2865
|
+
return /* @__PURE__ */ jsx8(FilloForm, { form: memo2.current.form, client, ...rest });
|
|
2441
2866
|
}
|
|
2442
2867
|
function defineFormFromJsx(element) {
|
|
2443
2868
|
if (!element || element.type !== JsxForm) {
|
|
@@ -2481,7 +2906,7 @@ var Fillo = {
|
|
|
2481
2906
|
};
|
|
2482
2907
|
|
|
2483
2908
|
// src/provider.tsx
|
|
2484
|
-
import { useId as useId3, useMemo as useMemo6 } from "react";
|
|
2909
|
+
import { useId as useId3, useMemo as useMemo6, useState as useState7 } from "react";
|
|
2485
2910
|
import {
|
|
2486
2911
|
FilloError as FilloError3,
|
|
2487
2912
|
formSchemasEqual as formSchemasEqual2,
|
|
@@ -2490,7 +2915,7 @@ import {
|
|
|
2490
2915
|
resolveStrings as resolveStrings2,
|
|
2491
2916
|
syncCodeForm as syncCodeForm3
|
|
2492
2917
|
} from "@usefillo/core";
|
|
2493
|
-
import { Fragment as
|
|
2918
|
+
import { Fragment as Fragment4, jsx as jsx9 } from "react/jsx-runtime";
|
|
2494
2919
|
function FilloProvider({
|
|
2495
2920
|
children,
|
|
2496
2921
|
form,
|
|
@@ -2500,20 +2925,30 @@ function FilloProvider({
|
|
|
2500
2925
|
strings,
|
|
2501
2926
|
onError,
|
|
2502
2927
|
renderError,
|
|
2928
|
+
preview,
|
|
2503
2929
|
...options
|
|
2504
2930
|
}) {
|
|
2505
2931
|
const instanceId = useId3().replace(/:/g, "");
|
|
2932
|
+
const devEnv = useIsDevEnv();
|
|
2933
|
+
const devChrome = preview === true || devEnv;
|
|
2934
|
+
if (preview === true) warnPreviewInProduction();
|
|
2506
2935
|
const codeForm = isCodeForm2(form) ? form : null;
|
|
2507
2936
|
const {
|
|
2508
2937
|
formId: syncedFormId,
|
|
2509
2938
|
status: syncedStatus,
|
|
2510
2939
|
error: syncError,
|
|
2511
2940
|
syncing: codeFormSyncing,
|
|
2512
|
-
resolvedSchema
|
|
2941
|
+
resolvedSchema,
|
|
2942
|
+
accepting: syncedAccepting,
|
|
2943
|
+
acceptingReason: syncedAcceptingReason,
|
|
2944
|
+
uploadsAvailable,
|
|
2945
|
+
warningCode,
|
|
2946
|
+
warningUrl
|
|
2513
2947
|
} = useCodeFormSync(form, client, onError, {
|
|
2514
2948
|
allowUnsynced: Boolean(!client || formId || options.skipValidation)
|
|
2515
2949
|
});
|
|
2516
|
-
const
|
|
2950
|
+
const [resyncWarning, setResyncWarning] = useState7(null);
|
|
2951
|
+
const rawSchema = codeForm ? resolvedSchema && !devChrome ? resolvedSchema : codeForm.schema : form;
|
|
2517
2952
|
const normalized = useMemo6(() => normalizeFormSchema2(rawSchema), [rawSchema]);
|
|
2518
2953
|
if (!normalized.ok) {
|
|
2519
2954
|
throw new FilloError3(`This form could not be rendered: ${normalized.error}`, 422);
|
|
@@ -2523,6 +2958,11 @@ function FilloProvider({
|
|
|
2523
2958
|
() => codeForm && client?.key ? async () => {
|
|
2524
2959
|
try {
|
|
2525
2960
|
const result = await syncCodeForm3(client, codeForm, { bypassCache: true });
|
|
2961
|
+
setResyncWarning({
|
|
2962
|
+
code: result.warningCode,
|
|
2963
|
+
url: result.warningUrl,
|
|
2964
|
+
uploadsAvailable: result.uploadsAvailable
|
|
2965
|
+
});
|
|
2526
2966
|
if (result.status === "draft") {
|
|
2527
2967
|
throw new FilloError3(
|
|
2528
2968
|
"This form is no longer published. Submission was stopped before sending answers.",
|
|
@@ -2531,6 +2971,14 @@ function FilloProvider({
|
|
|
2531
2971
|
"form_not_published"
|
|
2532
2972
|
);
|
|
2533
2973
|
}
|
|
2974
|
+
if (result.accepting === false) {
|
|
2975
|
+
throw new FilloError3(
|
|
2976
|
+
"This form is no longer accepting responses. Submission was stopped before sending answers.",
|
|
2977
|
+
403,
|
|
2978
|
+
void 0,
|
|
2979
|
+
result.acceptingReason ?? "form_not_accepting"
|
|
2980
|
+
);
|
|
2981
|
+
}
|
|
2534
2982
|
const authoritative = normalizeFormSchema2(result.resolvedSchema ?? codeForm.schema);
|
|
2535
2983
|
if (!authoritative.ok) {
|
|
2536
2984
|
throw new FilloError3(
|
|
@@ -2578,25 +3026,43 @@ function FilloProvider({
|
|
|
2578
3026
|
form: schema,
|
|
2579
3027
|
formId: syncedFormId ?? formId,
|
|
2580
3028
|
resolveFormId,
|
|
3029
|
+
// Dev chrome (preview or dev env) surfaces the developer-grade resolution
|
|
3030
|
+
// failure (message + code); an explicit option wins.
|
|
3031
|
+
verboseResolutionErrors: options.verboseResolutionErrors ?? devChrome,
|
|
2581
3032
|
// Headless = your markup, no Fillo-rendered layout. Free like every embed
|
|
2582
3033
|
// method; recorded per response so usage stays measurable.
|
|
2583
3034
|
surface: "headless"
|
|
2584
3035
|
});
|
|
2585
3036
|
useDraftFlush(api);
|
|
3037
|
+
const freshWarningCode = resyncWarning ? resyncWarning.code : warningCode;
|
|
3038
|
+
const freshWarningUrl = resyncWarning ? resyncWarning.url : warningUrl;
|
|
3039
|
+
const freshUploadsAvailable = resyncWarning ? resyncWarning.uploadsAvailable : uploadsAvailable;
|
|
3040
|
+
const chromeValue = useMemo6(
|
|
3041
|
+
() => ({
|
|
3042
|
+
devChrome,
|
|
3043
|
+
uploadsAvailable: freshUploadsAvailable,
|
|
3044
|
+
warningCode: freshWarningCode,
|
|
3045
|
+
warningUrl: freshWarningUrl
|
|
3046
|
+
}),
|
|
3047
|
+
[devChrome, freshUploadsAvailable, freshWarningCode, freshWarningUrl]
|
|
3048
|
+
);
|
|
2586
3049
|
const resolvedStrings = resolveStrings2(strings);
|
|
2587
3050
|
const canonicalSyncError = syncError && !options.skipValidation ? syncError : null;
|
|
2588
|
-
if (canonicalSyncError && renderError) return /* @__PURE__ */
|
|
2589
|
-
if (canonicalSyncError && !
|
|
2590
|
-
if (codeFormSyncing && !options.skipValidation && !
|
|
3051
|
+
if (canonicalSyncError && renderError) return /* @__PURE__ */ jsx9(Fragment4, { children: renderError(canonicalSyncError) });
|
|
3052
|
+
if (canonicalSyncError && !devChrome) return null;
|
|
3053
|
+
if (codeFormSyncing && !options.skipValidation && !devChrome) return null;
|
|
2591
3054
|
const notPublished = Boolean(codeForm) && syncedStatus === "draft" && !options.skipValidation;
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
3055
|
+
const notAccepting = Boolean(codeForm) && syncedStatus === "published" && syncedAccepting === false && !options.skipValidation;
|
|
3056
|
+
if ((notPublished || notAccepting) && !devChrome) {
|
|
3057
|
+
const error = notPublished ? new FilloError3("This form isn't published yet.", 403, void 0, "form_not_published") : new FilloError3(
|
|
3058
|
+
"This form is no longer accepting responses.",
|
|
3059
|
+
403,
|
|
3060
|
+
void 0,
|
|
3061
|
+
syncedAcceptingReason ?? "form_not_accepting"
|
|
3062
|
+
);
|
|
3063
|
+
return renderError ? /* @__PURE__ */ jsx9(Fragment4, { children: renderError(error) }) : null;
|
|
2595
3064
|
}
|
|
2596
|
-
return /* @__PURE__ */
|
|
2597
|
-
}
|
|
2598
|
-
function isDevEnv3() {
|
|
2599
|
-
return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
3065
|
+
return /* @__PURE__ */ jsx9(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx9(FilloChromeContext.Provider, { value: chromeValue, children: /* @__PURE__ */ jsx9(FilloStringsContext.Provider, { value: resolvedStrings, children: /* @__PURE__ */ jsx9(FilloAppearanceContext.Provider, { value: appearance, children: /* @__PURE__ */ jsx9(FilloInstanceIdContext.Provider, { value: instanceId, children }) }) }) }) });
|
|
2600
3066
|
}
|
|
2601
3067
|
|
|
2602
3068
|
// src/index.ts
|