@usefillo/react 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  // src/FilloForm.tsx
4
4
  import {
5
- useCallback,
6
- useEffect as useEffect8,
5
+ useCallback as useCallback2,
6
+ useEffect as useEffect9,
7
7
  useId as useId2,
8
- useMemo as useMemo5,
9
- useRef as useRef8,
8
+ useMemo as useMemo6,
9
+ useRef as useRef9,
10
10
  useState as useState6,
11
11
  useSyncExternalStore as useSyncExternalStore2
12
12
  } from "react";
@@ -15,7 +15,6 @@ import {
15
15
  createClient,
16
16
  formSchemasEqual,
17
17
  isBuildTimeDevEnv as isBuildTimeDevEnv2,
18
- isField as isField2,
19
18
  isFilloError as isFilloError2,
20
19
  isLikelyDevEnv,
21
20
  needsExplicitSubmit,
@@ -25,7 +24,7 @@ import {
25
24
  reachablePageSequence,
26
25
  resolveThemeAppearance
27
26
  } from "@usefillo/core";
28
- import { REQUIRED_FIELD_MESSAGE as REQUIRED_FIELD_MESSAGE2, resolveStrings, slotClass as slotClass3 } from "@usefillo/core";
27
+ import { resolveStrings, slotClass as slotClass3 } from "@usefillo/core";
29
28
 
30
29
  // src/appearance.tsx
31
30
  import { createContext, useContext } from "react";
@@ -488,11 +487,176 @@ import {
488
487
  pipeBlock,
489
488
  radioGroupStep,
490
489
  REQUIRED_FIELD_MESSAGE,
490
+ requiredFieldMessage,
491
491
  shouldAutoSubmit,
492
492
  slotClass as slotClass2,
493
493
  visibleFields
494
494
  } from "@usefillo/core";
495
- import { memo, useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useMemo as useMemo4, useRef as useRef6, useState as useState5 } from "react";
495
+ import { memo, useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useMemo as useMemo5, useRef as useRef7, useState as useState5 } from "react";
496
+
497
+ // src/group.tsx
498
+ import { visibleGroupChildren } from "@usefillo/core";
499
+ import { useCallback, useContext as useContext3, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef3 } from "react";
500
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
501
+ var EMPTY_INSTANCE = {};
502
+ var FOCUSABLE_SELECTOR = 'input:not(:disabled), select:not(:disabled), textarea:not(:disabled), button:not(:disabled), [tabindex]:not([tabindex="-1"])';
503
+ function firstFieldControl(card) {
504
+ const candidates = card.querySelectorAll(FOCUSABLE_SELECTOR);
505
+ for (const el of candidates) {
506
+ if (!el.classList.contains("fillo-group-remove")) return el;
507
+ }
508
+ return null;
509
+ }
510
+ function materializeInstances(value, count) {
511
+ const base = Array.isArray(value) ? value : [];
512
+ const out = base.slice(0, count);
513
+ while (out.length < count) out.push({});
514
+ return out;
515
+ }
516
+ function GroupInstance({
517
+ group,
518
+ index,
519
+ count,
520
+ item,
521
+ instanceValue,
522
+ api,
523
+ atFloor,
524
+ onPatch,
525
+ onRemove,
526
+ cardRef
527
+ }) {
528
+ const strings = useStrings();
529
+ const groupId = group.id;
530
+ const n = index + 1;
531
+ const label = strings.groupInstanceLabel(item, n, count);
532
+ const { scopedChildren, data } = useMemo3(() => {
533
+ const prefix = `${groupId}.${index}.`;
534
+ const visible = visibleGroupChildren(group, instanceValue);
535
+ const scoped = [];
536
+ const scopedData = {};
537
+ for (const child of visible) {
538
+ const compoundId = prefix + child.id;
539
+ scoped.push({ ...child, id: compoundId });
540
+ scopedData[compoundId] = instanceValue[child.id];
541
+ }
542
+ return { scopedChildren: scoped, data: scopedData };
543
+ }, [group, instanceValue, groupId, index]);
544
+ const setValue = useCallback(
545
+ (fieldId, v) => {
546
+ const prefix = `${groupId}.${index}.`;
547
+ const childId = fieldId.startsWith(prefix) ? fieldId.slice(prefix.length) : fieldId;
548
+ onPatch(index, childId, v);
549
+ },
550
+ [groupId, index, onPatch]
551
+ );
552
+ const childApi = { ...api, data, errors: api.errors, setValue };
553
+ return /* @__PURE__ */ jsxs2("div", { ref: cardRef, className: "fillo-group-instance", role: "group", "aria-label": label, tabIndex: -1, children: [
554
+ /* @__PURE__ */ jsxs2("div", { className: "fillo-group-instance-header", children: [
555
+ /* @__PURE__ */ jsx2("p", { className: "fillo-group-instance-title", children: label }),
556
+ /* @__PURE__ */ jsx2(
557
+ "button",
558
+ {
559
+ type: "button",
560
+ className: "fillo-group-remove",
561
+ "aria-label": strings.groupRemoveLabel(item, n),
562
+ disabled: atFloor,
563
+ title: atFloor ? `At least ${Math.max(group.minInstances ?? 1, 0)} required` : void 0,
564
+ onClick: () => onRemove(index),
565
+ children: "\xD7"
566
+ }
567
+ )
568
+ ] }),
569
+ scopedChildren.map((child) => /* @__PURE__ */ jsx2(BlockRenderer, { block: child, api: childApi }, child.id))
570
+ ] });
571
+ }
572
+ function RepeatingGroup({ field, error, setValue, api, ids: providedIds }) {
573
+ const ids = useFilloFieldIds(field.id, providedIds);
574
+ const strings = useStrings();
575
+ const announce = useFilloAnnounce();
576
+ const group = field;
577
+ const item = group.itemLabel || group.label;
578
+ const liveApi = useContext3(FilloContext) ?? api;
579
+ const value = liveApi.data[field.id];
580
+ const storedInstances = Array.isArray(value) ? value : void 0;
581
+ const floor = Math.max(group.minInstances ?? 1, 0);
582
+ const renderedCount = Math.max(storedInstances?.length ?? 0, floor);
583
+ const atMax = renderedCount >= group.maxInstances;
584
+ const atFloor = renderedCount <= floor;
585
+ const instanceRefs = useRef3([]);
586
+ const addButtonRef = useRef3(null);
587
+ const pendingFocus = useRef3(null);
588
+ useEffect3(() => {
589
+ const pending = pendingFocus.current;
590
+ pendingFocus.current = null;
591
+ if (!pending) return;
592
+ if (pending.target === "add") {
593
+ addButtonRef.current?.focus();
594
+ return;
595
+ }
596
+ const card = instanceRefs.current[pending.index];
597
+ if (!card) return;
598
+ if (pending.target === "instance-card") {
599
+ card.focus();
600
+ return;
601
+ }
602
+ (firstFieldControl(card) ?? card).focus();
603
+ }, [value]);
604
+ function patchInstance(index, childId, v) {
605
+ const instances = materializeInstances(value, renderedCount);
606
+ instances[index] = { ...instances[index] ?? {}, [childId]: v };
607
+ setValue(instances);
608
+ }
609
+ function handleAdd() {
610
+ if (atMax) return;
611
+ const instances = materializeInstances(value, renderedCount);
612
+ instances.push({});
613
+ const n = instances.length;
614
+ pendingFocus.current = { target: "instance-control", index: n - 1 };
615
+ setValue(instances);
616
+ announce(strings.groupInstanceAdded(item, n, n));
617
+ }
618
+ function handleRemove(index) {
619
+ if (atFloor) return;
620
+ const instances = materializeInstances(value, renderedCount);
621
+ instances.splice(index, 1);
622
+ pendingFocus.current = index > 0 ? { target: "instance-card", index: index - 1 } : { target: "add" };
623
+ setValue(instances);
624
+ announce(strings.groupInstanceRemoved(item, instances.length));
625
+ }
626
+ return /* @__PURE__ */ jsx2(FieldShell, { field: group, error, ids, children: /* @__PURE__ */ jsxs2("div", { className: "fillo-group", children: [
627
+ Array.from({ length: renderedCount }, (_, index) => /* @__PURE__ */ jsx2(
628
+ GroupInstance,
629
+ {
630
+ group,
631
+ index,
632
+ count: renderedCount,
633
+ item,
634
+ instanceValue: storedInstances?.[index] ?? EMPTY_INSTANCE,
635
+ api: liveApi,
636
+ atFloor,
637
+ onPatch: patchInstance,
638
+ onRemove: handleRemove,
639
+ cardRef: (el) => {
640
+ instanceRefs.current[index] = el;
641
+ }
642
+ },
643
+ index
644
+ )),
645
+ /* @__PURE__ */ jsx2(
646
+ "button",
647
+ {
648
+ ref: addButtonRef,
649
+ type: "button",
650
+ className: "fillo-group-add",
651
+ disabled: atMax,
652
+ "aria-disabled": atMax ? true : void 0,
653
+ title: atMax ? `Maximum ${group.maxInstances} reached` : void 0,
654
+ onClick: handleAdd,
655
+ children: group.addLabel || strings.groupAdd
656
+ }
657
+ )
658
+ ] }) });
659
+ }
496
660
 
497
661
  // src/phone.tsx
498
662
  import {
@@ -507,8 +671,8 @@ import {
507
671
  positionPhonePopover,
508
672
  toE164
509
673
  } from "@usefillo/core";
510
- import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState2 } from "react";
511
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
674
+ import { useEffect as useEffect4, useLayoutEffect, useMemo as useMemo4, useRef as useRef4, useState as useState2 } from "react";
675
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
512
676
  function localeCountry() {
513
677
  if (typeof navigator === "undefined") return void 0;
514
678
  const tag = navigator.languages?.[0] || navigator.language || "";
@@ -537,13 +701,13 @@ function PhoneField({ field, value, error, setValue, ids: providedIds }) {
537
701
  const [country, setCountry] = useState2(
538
702
  () => parsePhone(stored).country ?? countryByIso(schema.defaultCountry) ?? PHONE_COUNTRIES[0]
539
703
  );
540
- const touched = useRef3(false);
541
- useEffect3(() => {
704
+ const touched = useRef4(false);
705
+ useEffect4(() => {
542
706
  if (touched.current || !stored) return;
543
707
  const restoredCountry = parsePhone(stored).country;
544
708
  if (restoredCountry) setCountry(restoredCountry);
545
709
  }, [stored]);
546
- useEffect3(() => {
710
+ useEffect4(() => {
547
711
  if (touched.current) return;
548
712
  if (parsePhone(stored).country || countryByIso(schema.defaultCountry)) return;
549
713
  const guess = countryByTimeZone(browserTimeZone()) ?? countryByIso(localeCountry());
@@ -552,8 +716,8 @@ function PhoneField({ field, value, error, setValue, ids: providedIds }) {
552
716
  const [pendingRaw, setPendingRaw] = useState2(null);
553
717
  const national = nationalFor(stored, country);
554
718
  const display = pendingRaw ?? formatNational(country, national);
555
- const inputRef = useRef3(null);
556
- const caretDigits = useRef3(null);
719
+ const inputRef = useRef4(null);
720
+ const caretDigits = useRef4(null);
557
721
  useLayoutEffect(() => {
558
722
  const el = inputRef.current;
559
723
  if (!el || caretDigits.current == null) return;
@@ -600,15 +764,15 @@ function PhoneField({ field, value, error, setValue, ids: providedIds }) {
600
764
  inputRef.current?.focus();
601
765
  }
602
766
  const describedBy = [field.description ? ids.descriptionId : null, error ? ids.errorId : null].filter(Boolean).join(" ") || void 0;
603
- return /* @__PURE__ */ jsxs2("div", { ...slots.wrapperProps(`fillo-field fillo-field--phone${error ? " fillo-field--error" : ""}`), children: [
604
- /* @__PURE__ */ jsxs2("label", { className: slots.label, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
767
+ return /* @__PURE__ */ jsxs3("div", { ...slots.wrapperProps(`fillo-field fillo-field--phone${error ? " fillo-field--error" : ""}`), children: [
768
+ /* @__PURE__ */ jsxs3("label", { className: slots.label, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
605
769
  field.label,
606
- !field.required && /* @__PURE__ */ jsx2("span", { className: "fillo-optional", children: strings.optional })
770
+ !field.required && /* @__PURE__ */ jsx3("span", { className: "fillo-optional", children: strings.optional })
607
771
  ] }),
608
- field.description && /* @__PURE__ */ jsx2("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
609
- /* @__PURE__ */ jsxs2("div", { className: "fillo-phone", children: [
610
- /* @__PURE__ */ jsx2(CountrySelect, { country, onPick: pickCountry, labelledBy: ids.labelId }),
611
- /* @__PURE__ */ jsx2(
772
+ field.description && /* @__PURE__ */ jsx3("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
773
+ /* @__PURE__ */ jsxs3("div", { className: "fillo-phone", children: [
774
+ /* @__PURE__ */ jsx3(CountrySelect, { country, onPick: pickCountry, labelledBy: ids.labelId }),
775
+ /* @__PURE__ */ jsx3(
612
776
  "input",
613
777
  {
614
778
  ref: inputRef,
@@ -627,7 +791,7 @@ function PhoneField({ field, value, error, setValue, ids: providedIds }) {
627
791
  }
628
792
  )
629
793
  ] }),
630
- error && /* @__PURE__ */ jsx2("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
794
+ error && /* @__PURE__ */ jsx3("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
631
795
  ] });
632
796
  }
633
797
  function CountrySelect({
@@ -641,13 +805,13 @@ function CountrySelect({
641
805
  const [query, setQuery] = useState2("");
642
806
  const [active, setActive] = useState2(0);
643
807
  const [placement, setPlacement] = useState2("below");
644
- const rootRef = useRef3(null);
645
- const triggerRef = useRef3(null);
646
- const popoverRef = useRef3(null);
647
- const searchRef = useRef3(null);
648
- const seedRef = useRef3(null);
808
+ const rootRef = useRef4(null);
809
+ const triggerRef = useRef4(null);
810
+ const popoverRef = useRef4(null);
811
+ const searchRef = useRef4(null);
812
+ const seedRef = useRef4(null);
649
813
  const listId = `${labelledBy}-countries`;
650
- const matches = useMemo3(() => {
814
+ const matches = useMemo4(() => {
651
815
  const q = query.trim().toLowerCase();
652
816
  if (!q) return PHONE_PICKER_COUNTRIES;
653
817
  const qDigits = q.replace(/[^\d]/g, "");
@@ -655,7 +819,7 @@ function CountrySelect({
655
819
  (c) => c.name.toLowerCase().includes(q) || c.iso2.toLowerCase() === q || qDigits.length > 0 && c.dialCode.startsWith(qDigits)
656
820
  );
657
821
  }, [query]);
658
- useEffect3(() => {
822
+ useEffect4(() => {
659
823
  if (!open) return;
660
824
  const seed = seedRef.current ?? "";
661
825
  seedRef.current = null;
@@ -664,7 +828,7 @@ function CountrySelect({
664
828
  const id = requestAnimationFrame(() => searchRef.current?.focus());
665
829
  return () => cancelAnimationFrame(id);
666
830
  }, [open, country]);
667
- useEffect3(() => {
831
+ useEffect4(() => {
668
832
  if (!open) return;
669
833
  const onDown = (e) => {
670
834
  if (!rootRef.current?.contains(e.target)) setOpen(false);
@@ -683,12 +847,12 @@ function CountrySelect({
683
847
  window.removeEventListener("scroll", update, true);
684
848
  };
685
849
  }, [open, matches.length]);
686
- useEffect3(() => {
850
+ useEffect4(() => {
687
851
  if (!open) return;
688
852
  document.getElementById(`${listId}-opt-${active}`)?.scrollIntoView({ block: "nearest" });
689
853
  }, [active, open, listId]);
690
854
  const resultsAnnouncement = strings.phoneResultsCount(matches.length);
691
- useEffect3(() => {
855
+ useEffect4(() => {
692
856
  if (!open || !query.trim()) return;
693
857
  const id = setTimeout(() => announce(resultsAnnouncement), 300);
694
858
  return () => clearTimeout(id);
@@ -738,8 +902,8 @@ function CountrySelect({
738
902
  if (!rootRef.current?.contains(document.activeElement)) setOpen(false);
739
903
  });
740
904
  }
741
- return /* @__PURE__ */ jsxs2("div", { className: "fillo-phone-country", ref: rootRef, onBlur: onCompositeBlur, children: [
742
- /* @__PURE__ */ jsxs2(
905
+ return /* @__PURE__ */ jsxs3("div", { className: "fillo-phone-country", ref: rootRef, onBlur: onCompositeBlur, children: [
906
+ /* @__PURE__ */ jsxs3(
743
907
  "button",
744
908
  {
745
909
  ref: triggerRef,
@@ -752,17 +916,17 @@ function CountrySelect({
752
916
  onClick: () => setOpen((o) => !o),
753
917
  onKeyDown: onTriggerKeyDown,
754
918
  children: [
755
- /* @__PURE__ */ jsx2("span", { className: "fillo-phone-flag-emoji", "aria-hidden": "true", children: flagEmoji(country.iso2) }),
756
- /* @__PURE__ */ jsxs2("span", { className: "fillo-phone-dial", children: [
919
+ /* @__PURE__ */ jsx3("span", { className: "fillo-phone-flag-emoji", "aria-hidden": "true", children: flagEmoji(country.iso2) }),
920
+ /* @__PURE__ */ jsxs3("span", { className: "fillo-phone-dial", children: [
757
921
  "+",
758
922
  country.dialCode
759
923
  ] }),
760
- /* @__PURE__ */ jsx2("span", { className: "fillo-phone-caret", "aria-hidden": "true", children: "\u25BE" })
924
+ /* @__PURE__ */ jsx3("span", { className: "fillo-phone-caret", "aria-hidden": "true", children: "\u25BE" })
761
925
  ]
762
926
  }
763
927
  ),
764
- open && /* @__PURE__ */ jsxs2("div", { ref: popoverRef, className: `fillo-phone-popover fillo-phone-popover--${placement}`, children: [
765
- /* @__PURE__ */ jsx2(
928
+ open && /* @__PURE__ */ jsxs3("div", { ref: popoverRef, className: `fillo-phone-popover fillo-phone-popover--${placement}`, children: [
929
+ /* @__PURE__ */ jsx3(
766
930
  "input",
767
931
  {
768
932
  ref: searchRef,
@@ -783,8 +947,8 @@ function CountrySelect({
783
947
  onKeyDown: onSearchKey
784
948
  }
785
949
  ),
786
- /* @__PURE__ */ jsxs2("ul", { className: "fillo-phone-list", role: "listbox", id: listId, "aria-label": "Country", children: [
787
- matches.map((c, i) => /* @__PURE__ */ jsxs2(
950
+ /* @__PURE__ */ jsxs3("ul", { className: "fillo-phone-list", role: "listbox", id: listId, "aria-label": "Country", children: [
951
+ matches.map((c, i) => /* @__PURE__ */ jsxs3(
788
952
  "li",
789
953
  {
790
954
  id: `${listId}-opt-${i}`,
@@ -797,9 +961,9 @@ function CountrySelect({
797
961
  choose(c);
798
962
  },
799
963
  children: [
800
- /* @__PURE__ */ jsx2("span", { className: "fillo-phone-flag-emoji", "aria-hidden": "true", children: flagEmoji(c.iso2) }),
801
- /* @__PURE__ */ jsx2("span", { className: "fillo-phone-option-name", children: c.name }),
802
- /* @__PURE__ */ jsxs2("span", { className: "fillo-phone-option-dial", children: [
964
+ /* @__PURE__ */ jsx3("span", { className: "fillo-phone-flag-emoji", "aria-hidden": "true", children: flagEmoji(c.iso2) }),
965
+ /* @__PURE__ */ jsx3("span", { className: "fillo-phone-option-name", children: c.name }),
966
+ /* @__PURE__ */ jsxs3("span", { className: "fillo-phone-option-dial", children: [
803
967
  "+",
804
968
  c.dialCode
805
969
  ] })
@@ -807,26 +971,26 @@ function CountrySelect({
807
971
  },
808
972
  c.iso2
809
973
  )),
810
- matches.length === 0 && /* @__PURE__ */ jsx2("li", { className: "fillo-phone-empty", role: "option", "aria-disabled": "true", children: "No matches" })
974
+ matches.length === 0 && /* @__PURE__ */ jsx3("li", { className: "fillo-phone-empty", role: "option", "aria-disabled": "true", children: "No matches" })
811
975
  ] })
812
976
  ] })
813
977
  ] });
814
978
  }
815
979
 
816
980
  // src/signature.tsx
817
- import { useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
818
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
981
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState3 } from "react";
982
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
819
983
  function SignatureField({ field, value, error, setValue, ids: providedIds }) {
820
984
  const ids = useFilloFieldIds(field.id, providedIds);
821
985
  const slots = useFieldSlots(field, error);
822
986
  const strings = useStrings();
823
987
  const typedInputId = `${ids.inputId}-typed`;
824
- const canvasRef = useRef4(null);
825
- const typedInputRef = useRef4(null);
826
- const drawing = useRef4(false);
988
+ const canvasRef = useRef5(null);
989
+ const typedInputRef = useRef5(null);
990
+ const drawing = useRef5(false);
827
991
  const [hasInk, setHasInk] = useState3(Boolean(value));
828
992
  const [typedName, setTypedName] = useState3("");
829
- useEffect4(() => {
993
+ useEffect5(() => {
830
994
  const canvas = canvasRef.current;
831
995
  if (!canvas) return;
832
996
  const scale = window.devicePixelRatio || 1;
@@ -841,7 +1005,7 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
841
1005
  ctx.lineJoin = "round";
842
1006
  ctx.strokeStyle = getComputedStyle(canvas).color;
843
1007
  }, []);
844
- useEffect4(() => {
1008
+ useEffect5(() => {
845
1009
  const canvas = canvasRef.current;
846
1010
  const ctx = canvas?.getContext("2d");
847
1011
  if (!canvas || !ctx) return;
@@ -922,21 +1086,21 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
922
1086
  typedInputRef.current?.focus();
923
1087
  }
924
1088
  const describedBy = [field.description ? ids.descriptionId : null, error ? ids.errorId : null].filter(Boolean).join(" ") || void 0;
925
- return /* @__PURE__ */ jsxs3("div", { ...slots.wrapperProps(`fillo-field fillo-field--signature${error ? " fillo-field--error" : ""}`), children: [
926
- /* @__PURE__ */ jsxs3("span", { className: slots.label, "data-fillo": "label", id: ids.labelId, children: [
1089
+ return /* @__PURE__ */ jsxs4("div", { ...slots.wrapperProps(`fillo-field fillo-field--signature${error ? " fillo-field--error" : ""}`), children: [
1090
+ /* @__PURE__ */ jsxs4("span", { className: slots.label, "data-fillo": "label", id: ids.labelId, children: [
927
1091
  field.label,
928
- !field.required && /* @__PURE__ */ jsx3("span", { className: "fillo-optional", children: strings.optional })
1092
+ !field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children: strings.optional })
929
1093
  ] }),
930
- field.description && /* @__PURE__ */ jsx3("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
931
- /* @__PURE__ */ jsxs3(
1094
+ field.description && /* @__PURE__ */ jsx4("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
1095
+ /* @__PURE__ */ jsxs4(
932
1096
  "div",
933
1097
  {
934
1098
  role: "group",
935
1099
  "aria-labelledby": ids.labelId,
936
1100
  "aria-describedby": describedBy,
937
1101
  children: [
938
- /* @__PURE__ */ jsxs3("div", { className: slots.control("fillo-signature"), "data-fillo": "control", children: [
939
- /* @__PURE__ */ jsx3(
1102
+ /* @__PURE__ */ jsxs4("div", { className: slots.control("fillo-signature"), "data-fillo": "control", children: [
1103
+ /* @__PURE__ */ jsx4(
940
1104
  "canvas",
941
1105
  {
942
1106
  ref: canvasRef,
@@ -950,12 +1114,12 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
950
1114
  onPointerLeave: end
951
1115
  }
952
1116
  ),
953
- hasInk && /* @__PURE__ */ jsx3("button", { type: "button", className: "fillo-signature-clear", onClick: clear, children: "Clear" }),
954
- !hasInk && /* @__PURE__ */ jsx3("span", { className: "fillo-signature-hint", "aria-hidden": "true", children: "Sign here" })
1117
+ hasInk && /* @__PURE__ */ jsx4("button", { type: "button", className: "fillo-signature-clear", onClick: clear, children: "Clear" }),
1118
+ !hasInk && /* @__PURE__ */ jsx4("span", { className: "fillo-signature-hint", "aria-hidden": "true", children: "Sign here" })
955
1119
  ] }),
956
- /* @__PURE__ */ jsxs3("div", { className: "fillo-signature-type", children: [
957
- /* @__PURE__ */ jsx3("label", { className: "fillo-signature-type-label", htmlFor: typedInputId, children: "Or type your full name to sign" }),
958
- /* @__PURE__ */ jsx3(
1120
+ /* @__PURE__ */ jsxs4("div", { className: "fillo-signature-type", children: [
1121
+ /* @__PURE__ */ jsx4("label", { className: "fillo-signature-type-label", htmlFor: typedInputId, children: "Or type your full name to sign" }),
1122
+ /* @__PURE__ */ jsx4(
959
1123
  "input",
960
1124
  {
961
1125
  id: typedInputId,
@@ -978,13 +1142,13 @@ function SignatureField({ field, value, error, setValue, ids: providedIds }) {
978
1142
  ]
979
1143
  }
980
1144
  ),
981
- error && /* @__PURE__ */ jsx3("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
1145
+ error && /* @__PURE__ */ jsx4("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
982
1146
  ] });
983
1147
  }
984
1148
 
985
1149
  // src/upload.tsx
986
- import { useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
987
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1150
+ import { useEffect as useEffect6, useRef as useRef6, useState as useState4 } from "react";
1151
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
988
1152
  function uploadStatus(inFlight, doneCount, strings) {
989
1153
  const failed = inFlight.filter((f) => f.error).length;
990
1154
  const active = inFlight.length - failed;
@@ -1008,6 +1172,76 @@ function uploadFailureMessage(error, strings) {
1008
1172
  }
1009
1173
  return strings.uploadFailed;
1010
1174
  }
1175
+ function FileStateIcon({ state }) {
1176
+ if (state === "done") {
1177
+ return /* @__PURE__ */ jsx5("span", { className: "fillo-file-state fillo-file-state--done", "aria-hidden": "true", children: /* @__PURE__ */ jsxs5(
1178
+ "svg",
1179
+ {
1180
+ className: "fillo-file-state-icon",
1181
+ viewBox: "0 0 24 24",
1182
+ width: "22",
1183
+ height: "22",
1184
+ fill: "none",
1185
+ "aria-hidden": "true",
1186
+ focusable: "false",
1187
+ children: [
1188
+ /* @__PURE__ */ jsx5("path", { d: "M3.75 12a8.25 8.25 0 1 0 16.5 0 8.25 8.25 0 1 0-16.5 0" }),
1189
+ /* @__PURE__ */ jsx5("path", { d: "m8.5 12 2.25 2.25 4.75-5" })
1190
+ ]
1191
+ }
1192
+ ) });
1193
+ }
1194
+ if (state === "failed") {
1195
+ return /* @__PURE__ */ jsx5("span", { className: "fillo-file-state fillo-file-state--failed", "aria-hidden": "true", children: /* @__PURE__ */ jsxs5(
1196
+ "svg",
1197
+ {
1198
+ className: "fillo-file-state-icon",
1199
+ viewBox: "0 0 24 24",
1200
+ width: "22",
1201
+ height: "22",
1202
+ fill: "none",
1203
+ "aria-hidden": "true",
1204
+ focusable: "false",
1205
+ children: [
1206
+ /* @__PURE__ */ jsx5("path", { d: "M3.75 12a8.25 8.25 0 1 0 16.5 0 8.25 8.25 0 1 0-16.5 0" }),
1207
+ /* @__PURE__ */ jsx5("path", { d: "M12 7.75v5.5" }),
1208
+ /* @__PURE__ */ jsx5("path", { d: "M12 16.5h.01" })
1209
+ ]
1210
+ }
1211
+ ) });
1212
+ }
1213
+ return /* @__PURE__ */ jsx5("span", { className: "fillo-file-state fillo-file-state--uploading", "aria-hidden": "true", children: /* @__PURE__ */ jsxs5(
1214
+ "svg",
1215
+ {
1216
+ className: "fillo-file-state-icon",
1217
+ viewBox: "0 0 24 24",
1218
+ width: "22",
1219
+ height: "22",
1220
+ fill: "none",
1221
+ "aria-hidden": "true",
1222
+ focusable: "false",
1223
+ children: [
1224
+ /* @__PURE__ */ jsx5("path", { d: "M7.25 3.75h6.5l3 3v13.5h-9.5z" }),
1225
+ /* @__PURE__ */ jsx5("path", { d: "M13.75 3.75v3h3" })
1226
+ ]
1227
+ }
1228
+ ) });
1229
+ }
1230
+ function CloseIcon() {
1231
+ return /* @__PURE__ */ jsx5(
1232
+ "svg",
1233
+ {
1234
+ className: "fillo-file-action-icon",
1235
+ viewBox: "0 0 24 24",
1236
+ width: "16",
1237
+ height: "16",
1238
+ fill: "none",
1239
+ "aria-hidden": "true",
1240
+ focusable: "false",
1241
+ children: /* @__PURE__ */ jsx5("path", { d: "m7 7 10 10M17 7 7 17" })
1242
+ }
1243
+ );
1244
+ }
1011
1245
  function FileUploadField({
1012
1246
  field,
1013
1247
  value,
@@ -1021,15 +1255,15 @@ function FileUploadField({
1021
1255
  const strings = useStrings();
1022
1256
  const chrome = useFilloChrome();
1023
1257
  const schema = field;
1024
- const inputRef = useRef5(null);
1258
+ const inputRef = useRef6(null);
1025
1259
  const [inFlight, setInFlight] = useState4([]);
1026
1260
  const [dragOver, setDragOver] = useState4(false);
1027
- const mountedRef = useRef5(true);
1028
- const controllers = useRef5(/* @__PURE__ */ new Map());
1029
- const inFlightCount = useRef5(0);
1261
+ const mountedRef = useRef6(true);
1262
+ const controllers = useRef6(/* @__PURE__ */ new Map());
1263
+ const inFlightCount = useRef6(0);
1030
1264
  const setUploading = api.setUploading;
1031
1265
  const fieldId = field.id;
1032
- useEffect5(() => {
1266
+ useEffect6(() => {
1033
1267
  mountedRef.current = true;
1034
1268
  return () => {
1035
1269
  mountedRef.current = false;
@@ -1038,7 +1272,7 @@ function FileUploadField({
1038
1272
  };
1039
1273
  }, [fieldId, setUploading]);
1040
1274
  const files = Array.isArray(value) ? value : [];
1041
- const filesRef = useRef5(files);
1275
+ const filesRef = useRef6(files);
1042
1276
  filesRef.current = files;
1043
1277
  const maxFiles = schema.maxFiles ?? 1;
1044
1278
  const configuredMaxMb = schema.maxFileSizeMb ?? 500;
@@ -1115,19 +1349,19 @@ function FileUploadField({
1115
1349
  setValue(next);
1116
1350
  }
1117
1351
  const describedBy = [field.description ? ids.descriptionId : null, error ? ids.errorId : null].filter(Boolean).join(" ") || void 0;
1118
- return /* @__PURE__ */ jsxs4(
1352
+ return /* @__PURE__ */ jsxs5(
1119
1353
  "div",
1120
1354
  {
1121
1355
  ...slots.wrapperProps(
1122
1356
  `fillo-field fillo-field--file_upload${error ? " fillo-field--error" : ""}`
1123
1357
  ),
1124
1358
  children: [
1125
- /* @__PURE__ */ jsxs4("label", { className: slots.label, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
1359
+ /* @__PURE__ */ jsxs5("label", { className: slots.label, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
1126
1360
  field.label,
1127
- !field.required && /* @__PURE__ */ jsx4("span", { className: "fillo-optional", children: strings.optional })
1361
+ !field.required && /* @__PURE__ */ jsx5("span", { className: "fillo-optional", children: strings.optional })
1128
1362
  ] }),
1129
- field.description && /* @__PURE__ */ jsx4("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
1130
- remaining > 0 && /* @__PURE__ */ jsxs4(
1363
+ field.description && /* @__PURE__ */ jsx5("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
1364
+ remaining > 0 && /* @__PURE__ */ jsxs5(
1131
1365
  "div",
1132
1366
  {
1133
1367
  "data-fillo": "control",
@@ -1159,7 +1393,7 @@ function FileUploadField({
1159
1393
  }
1160
1394
  },
1161
1395
  children: [
1162
- /* @__PURE__ */ jsx4(
1396
+ /* @__PURE__ */ jsx5(
1163
1397
  "input",
1164
1398
  {
1165
1399
  ref: inputRef,
@@ -1172,20 +1406,20 @@ function FileUploadField({
1172
1406
  onChange: (e) => canUpload && handleFiles(e.target.files)
1173
1407
  }
1174
1408
  ),
1175
- canUpload ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1176
- /* @__PURE__ */ jsx4("span", { className: "fillo-dropzone-title", children: strings.dropzoneTitle(maxFiles > 1) }),
1177
- /* @__PURE__ */ jsx4("span", { className: "fillo-dropzone-hint", children: strings.dropzoneHint(maxMb) })
1178
- ] }) : storageBlocked && chrome?.devChrome ? /* @__PURE__ */ jsxs4("span", { className: "fillo-dropzone-hint", children: [
1409
+ canUpload ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1410
+ /* @__PURE__ */ jsx5("span", { className: "fillo-dropzone-title", children: strings.dropzoneTitle(maxFiles > 1) }),
1411
+ /* @__PURE__ */ jsx5("span", { className: "fillo-dropzone-hint", children: strings.dropzoneHint(maxMb) })
1412
+ ] }) : storageBlocked && chrome?.devChrome ? /* @__PURE__ */ jsxs5("span", { className: "fillo-dropzone-hint", children: [
1179
1413
  "Connect file storage to enable uploads",
1180
- storageUrl && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1414
+ storageUrl && /* @__PURE__ */ jsxs5(Fragment2, { children: [
1181
1415
  " \u2014 ",
1182
- /* @__PURE__ */ jsx4("a", { href: storageUrl, target: "_blank", rel: "noopener noreferrer", children: storageUrl })
1416
+ /* @__PURE__ */ jsx5("a", { href: storageUrl, target: "_blank", rel: "noopener noreferrer", children: storageUrl })
1183
1417
  ] })
1184
- ] }) : /* @__PURE__ */ jsx4("span", { className: "fillo-dropzone-hint", children: renderOnly ? strings.uploadsRenderOnly : storageBlocked ? strings.uploadsUnavailable : strings.uploadsDisabled })
1418
+ ] }) : /* @__PURE__ */ jsx5("span", { className: "fillo-dropzone-hint", children: renderOnly ? strings.uploadsRenderOnly : storageBlocked ? strings.uploadsUnavailable : strings.uploadsDisabled })
1185
1419
  ]
1186
1420
  }
1187
1421
  ),
1188
- /* @__PURE__ */ jsx4(
1422
+ /* @__PURE__ */ jsx5(
1189
1423
  "div",
1190
1424
  {
1191
1425
  role: "status",
@@ -1195,101 +1429,102 @@ function FileUploadField({
1195
1429
  children: uploadStatus(inFlight, files.length, strings)
1196
1430
  }
1197
1431
  ),
1198
- (inFlight.length > 0 || files.length > 0) && /* @__PURE__ */ jsxs4("ul", { className: "fillo-files", children: [
1199
- files.map((f) => /* @__PURE__ */ jsxs4("li", { className: "fillo-file fillo-file--done", children: [
1200
- /* @__PURE__ */ jsx4("span", { className: "fillo-file-name", children: f.name }),
1201
- /* @__PURE__ */ jsx4("span", { className: "fillo-file-meta", children: formatBytes(f.size) }),
1202
- /* @__PURE__ */ jsx4("span", { className: "fillo-sr-only", children: "Uploaded" }),
1203
- /* @__PURE__ */ jsx4(
1432
+ (inFlight.length > 0 || files.length > 0) && /* @__PURE__ */ jsxs5("ul", { className: "fillo-files", children: [
1433
+ files.map((f) => /* @__PURE__ */ jsxs5("li", { className: "fillo-file fillo-file--done", children: [
1434
+ /* @__PURE__ */ jsx5(FileStateIcon, { state: "done" }),
1435
+ /* @__PURE__ */ jsxs5("span", { className: "fillo-file-content", children: [
1436
+ /* @__PURE__ */ jsx5("span", { className: "fillo-file-name", children: f.name }),
1437
+ /* @__PURE__ */ jsx5("span", { className: "fillo-file-meta", children: strings.uploadedFile(formatBytes(f.size)) })
1438
+ ] }),
1439
+ /* @__PURE__ */ jsx5("span", { className: "fillo-file-actions", children: /* @__PURE__ */ jsx5(
1204
1440
  "button",
1205
1441
  {
1206
1442
  type: "button",
1207
1443
  className: "fillo-file-remove",
1208
- "aria-label": `Remove ${f.name}`,
1444
+ "aria-label": `${strings.uploadRemove} ${f.name}`,
1209
1445
  onClick: () => removeFile(f.fileId),
1210
- children: "\xD7"
1446
+ children: /* @__PURE__ */ jsx5(CloseIcon, {})
1211
1447
  }
1212
- )
1448
+ ) })
1213
1449
  ] }, f.fileId)),
1214
- inFlight.map((f) => /* @__PURE__ */ jsxs4(
1215
- "li",
1216
- {
1217
- className: `fillo-file${f.error ? " fillo-file--failed" : ""}`,
1218
- ...f.error ? {} : {
1219
- role: "progressbar",
1220
- "aria-label": `Uploading ${f.name}`,
1221
- "aria-valuemin": 0,
1222
- "aria-valuemax": 100,
1223
- "aria-valuenow": Math.round(f.fraction * 100)
1224
- },
1225
- children: [
1226
- /* @__PURE__ */ jsx4("span", { className: "fillo-file-name", children: f.name }),
1227
- f.error ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1228
- /* @__PURE__ */ jsxs4("span", { className: "fillo-file-actions", children: [
1229
- f.file && !f.tooLarge && remaining > 0 && /* @__PURE__ */ jsx4(
1230
- "button",
1231
- {
1232
- type: "button",
1233
- className: "fillo-file-retry",
1234
- "aria-label": `${strings.uploadRetry} ${f.name}`,
1235
- onClick: () => {
1236
- const retryFile = f.file;
1237
- if (!retryFile) return;
1238
- setInFlight((prev) => prev.filter((x) => x.key !== f.key));
1239
- void startUpload(retryFile);
1240
- },
1241
- children: strings.uploadRetry
1242
- }
1243
- ),
1244
- /* @__PURE__ */ jsx4(
1245
- "button",
1246
- {
1247
- type: "button",
1248
- className: "fillo-file-remove",
1249
- "aria-label": "Dismiss",
1250
- onClick: () => setInFlight((prev) => prev.filter((x) => x.key !== f.key)),
1251
- children: "\xD7"
1252
- }
1253
- )
1254
- ] }),
1255
- /* @__PURE__ */ jsx4("span", { className: "fillo-file-error", role: "alert", children: f.error })
1256
- ] }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
1257
- /* @__PURE__ */ jsxs4("span", { className: "fillo-file-meta", children: [
1258
- Math.round(f.fraction * 100),
1259
- "% of ",
1260
- formatBytes(f.size)
1261
- ] }),
1262
- /* @__PURE__ */ jsx4(
1263
- "button",
1264
- {
1265
- type: "button",
1266
- className: "fillo-file-remove",
1267
- "aria-label": `Cancel ${f.name}`,
1268
- onClick: () => cancelUpload(f.key),
1269
- children: "\xD7"
1270
- }
1271
- ),
1272
- /* @__PURE__ */ jsx4("span", { className: "fillo-progress", "aria-hidden": "true", children: /* @__PURE__ */ jsx4(
1450
+ inFlight.map((f) => /* @__PURE__ */ jsxs5("li", { className: `fillo-file${f.error ? " fillo-file--failed" : ""}`, children: [
1451
+ /* @__PURE__ */ jsx5(FileStateIcon, { state: f.error ? "failed" : "uploading" }),
1452
+ f.error ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1453
+ /* @__PURE__ */ jsxs5("span", { className: "fillo-file-content", children: [
1454
+ /* @__PURE__ */ jsx5("span", { className: "fillo-file-name", children: f.name }),
1455
+ /* @__PURE__ */ jsx5("span", { className: "fillo-file-error", role: "alert", children: f.error })
1456
+ ] }),
1457
+ /* @__PURE__ */ jsxs5("span", { className: "fillo-file-actions", children: [
1458
+ f.file && !f.tooLarge && remaining > 0 && /* @__PURE__ */ jsx5(
1459
+ "button",
1460
+ {
1461
+ type: "button",
1462
+ className: "fillo-file-retry",
1463
+ "aria-label": `${strings.uploadRetry} ${f.name}`,
1464
+ onClick: () => {
1465
+ const retryFile = f.file;
1466
+ if (!retryFile) return;
1467
+ setInFlight((prev) => prev.filter((x) => x.key !== f.key));
1468
+ void startUpload(retryFile);
1469
+ },
1470
+ children: strings.uploadRetry
1471
+ }
1472
+ ),
1473
+ /* @__PURE__ */ jsx5(
1474
+ "button",
1475
+ {
1476
+ type: "button",
1477
+ className: "fillo-file-remove",
1478
+ "aria-label": `${strings.uploadDismiss} ${f.name}`,
1479
+ onClick: () => setInFlight((prev) => prev.filter((x) => x.key !== f.key)),
1480
+ children: /* @__PURE__ */ jsx5(CloseIcon, {})
1481
+ }
1482
+ )
1483
+ ] })
1484
+ ] }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
1485
+ /* @__PURE__ */ jsxs5("span", { className: "fillo-file-content", children: [
1486
+ /* @__PURE__ */ jsx5("span", { className: "fillo-file-name", children: f.name }),
1487
+ /* @__PURE__ */ jsx5("span", { className: "fillo-file-meta", children: strings.uploadingFile(Math.round(f.fraction * 100), formatBytes(f.size)) })
1488
+ ] }),
1489
+ /* @__PURE__ */ jsx5("span", { className: "fillo-file-actions", children: /* @__PURE__ */ jsx5(
1490
+ "button",
1491
+ {
1492
+ type: "button",
1493
+ className: "fillo-file-remove",
1494
+ "aria-label": `${strings.uploadCancel} ${f.name}`,
1495
+ onClick: () => cancelUpload(f.key),
1496
+ children: /* @__PURE__ */ jsx5(CloseIcon, {})
1497
+ }
1498
+ ) }),
1499
+ /* @__PURE__ */ jsx5(
1500
+ "span",
1501
+ {
1502
+ className: "fillo-progress",
1503
+ role: "progressbar",
1504
+ "aria-label": `Uploading ${f.name}`,
1505
+ "aria-valuemin": 0,
1506
+ "aria-valuemax": 100,
1507
+ "aria-valuenow": Math.round(f.fraction * 100),
1508
+ children: /* @__PURE__ */ jsx5(
1273
1509
  "span",
1274
1510
  {
1275
1511
  className: "fillo-progress-bar",
1276
1512
  style: { width: `${f.fraction * 100}%` }
1277
1513
  }
1278
- ) })
1279
- ] })
1280
- ]
1281
- },
1282
- f.key
1283
- ))
1514
+ )
1515
+ }
1516
+ )
1517
+ ] })
1518
+ ] }, f.key))
1284
1519
  ] }),
1285
- error && /* @__PURE__ */ jsx4("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
1520
+ error && /* @__PURE__ */ jsx5("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
1286
1521
  ]
1287
1522
  }
1288
1523
  );
1289
1524
  }
1290
1525
 
1291
1526
  // src/fields.tsx
1292
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1527
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1293
1528
  function fieldAria(field, error, ids, opts = {}) {
1294
1529
  const describedBy = [
1295
1530
  field.description ? ids.descriptionId : null,
@@ -1334,7 +1569,7 @@ function FieldShell({
1334
1569
  const descriptionCls = useSlotClass("fillo-description", { slot: "fieldDescription", ...state });
1335
1570
  const errorCls = useSlotClass("fillo-error", { slot: "error", ...state });
1336
1571
  const strings = useStrings();
1337
- return /* @__PURE__ */ jsxs5(
1572
+ return /* @__PURE__ */ jsxs6(
1338
1573
  "div",
1339
1574
  {
1340
1575
  className: fieldCls,
@@ -1344,13 +1579,13 @@ function FieldShell({
1344
1579
  "data-invalid": error ? "" : void 0,
1345
1580
  "data-required": field.required ? "" : void 0,
1346
1581
  children: [
1347
- /* @__PURE__ */ jsxs5("label", { className: labelCls, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
1582
+ /* @__PURE__ */ jsxs6("label", { className: labelCls, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: [
1348
1583
  field.label,
1349
- !field.required && /* @__PURE__ */ jsx5("span", { className: "fillo-optional", children: strings.optional })
1584
+ !field.required && !(field.kind === "repeating_group" && (field.minInstances ?? 1) > 0) && /* @__PURE__ */ jsx6("span", { className: "fillo-optional", children: strings.optional })
1350
1585
  ] }),
1351
- field.description && /* @__PURE__ */ jsx5("p", { className: descriptionCls, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
1586
+ field.description && /* @__PURE__ */ jsx6("p", { className: descriptionCls, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
1352
1587
  children,
1353
- error && /* @__PURE__ */ jsx5("p", { className: errorCls, "data-fillo": "error", id: ids.errorId, children: error })
1588
+ error && /* @__PURE__ */ jsx6("p", { className: errorCls, "data-fillo": "error", id: ids.errorId, children: error })
1354
1589
  ]
1355
1590
  }
1356
1591
  );
@@ -1360,7 +1595,7 @@ function TextInput({ field, value, error, setValue, ids: providedIds }) {
1360
1595
  const type = field.kind === "email" ? "email" : field.kind === "url" ? "url" : field.kind === "number" ? "number" : field.kind === "date" ? "date" : "text";
1361
1596
  const autoComplete = field.kind === "email" ? "email" : field.kind === "url" ? "url" : void 0;
1362
1597
  const control = useControl("fillo-input", field, error);
1363
- return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
1598
+ return /* @__PURE__ */ jsx6(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx6(
1364
1599
  "input",
1365
1600
  {
1366
1601
  id: ids.inputId,
@@ -1381,8 +1616,8 @@ function NumberInput({ field, value, error, setValue, api, ids: providedIds }) {
1381
1616
  const ids = useFilloFieldIds(field.id, providedIds);
1382
1617
  const control = useControl("fillo-input", field, error);
1383
1618
  const [draft, setDraft] = useState5(null);
1384
- const inputRef = useRef6(null);
1385
- const pendingCaret = useRef6(null);
1619
+ const inputRef = useRef7(null);
1620
+ const pendingCaret = useRef7(null);
1386
1621
  const [, setRevertTick] = useState5(0);
1387
1622
  useLayoutEffect2(() => {
1388
1623
  if (pendingCaret.current === null) return;
@@ -1395,7 +1630,7 @@ function NumberInput({ field, value, error, setValue, api, ids: providedIds }) {
1395
1630
  const locale = localeForNotation(num.notation);
1396
1631
  const hasAffix = Boolean(num.prefix) || Boolean(num.suffix);
1397
1632
  if (!grouped && !hasAffix) {
1398
- return /* @__PURE__ */ jsx5(
1633
+ return /* @__PURE__ */ jsx6(
1399
1634
  TextInput,
1400
1635
  {
1401
1636
  field,
@@ -1410,7 +1645,7 @@ function NumberInput({ field, value, error, setValue, api, ids: providedIds }) {
1410
1645
  const stored = typeof value === "string" || typeof value === "number" ? String(value) : "";
1411
1646
  const n = Number(stored);
1412
1647
  const display = draft !== null ? draft : grouped && stored !== "" && Number.isFinite(n) ? formatGroupedNumber(n, { locale, decimals: num.decimals }) : stored;
1413
- const input = /* @__PURE__ */ jsx5(
1648
+ const input = /* @__PURE__ */ jsx6(
1414
1649
  "input",
1415
1650
  {
1416
1651
  ref: inputRef,
@@ -1437,16 +1672,16 @@ function NumberInput({ field, value, error, setValue, api, ids: providedIds }) {
1437
1672
  ...fieldAria(field, error, ids)
1438
1673
  }
1439
1674
  );
1440
- return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: hasAffix ? /* @__PURE__ */ jsxs5("div", { className: "fillo-number", children: [
1441
- num.prefix && /* @__PURE__ */ jsx5("span", { className: "fillo-number-prefix", children: num.prefix }),
1675
+ return /* @__PURE__ */ jsx6(FieldShell, { field, error, ids, children: hasAffix ? /* @__PURE__ */ jsxs6("div", { className: "fillo-number", children: [
1676
+ num.prefix && /* @__PURE__ */ jsx6("span", { className: "fillo-number-prefix", children: num.prefix }),
1442
1677
  input,
1443
- num.suffix && /* @__PURE__ */ jsx5("span", { className: "fillo-number-suffix", children: num.suffix })
1678
+ num.suffix && /* @__PURE__ */ jsx6("span", { className: "fillo-number-suffix", children: num.suffix })
1444
1679
  ] }) : input });
1445
1680
  }
1446
1681
  function LongText({ field, value, error, setValue, ids: providedIds }) {
1447
1682
  const ids = useFilloFieldIds(field.id, providedIds);
1448
1683
  const control = useControl("fillo-input fillo-textarea", field, error);
1449
- return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
1684
+ return /* @__PURE__ */ jsx6(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx6(
1450
1685
  "textarea",
1451
1686
  {
1452
1687
  id: ids.inputId,
@@ -1463,7 +1698,7 @@ function LongText({ field, value, error, setValue, ids: providedIds }) {
1463
1698
  function useDisplayOptions(choice) {
1464
1699
  const idsKey = choice.options.map((o) => o.id).join("\0");
1465
1700
  const [order, setOrder] = useState5(null);
1466
- useEffect6(() => {
1701
+ useEffect7(() => {
1467
1702
  if (!choice.shuffleOptions) return;
1468
1703
  const ids = idsKey === "" ? [] : idsKey.split("\0");
1469
1704
  for (let i = ids.length - 1; i > 0; i--) {
@@ -1483,7 +1718,7 @@ var OPTION_ICON_PATHS = {
1483
1718
  };
1484
1719
  function ChoiceIcon({ icon }) {
1485
1720
  if (!icon) return null;
1486
- return /* @__PURE__ */ jsx5(
1721
+ return /* @__PURE__ */ jsx6(
1487
1722
  "svg",
1488
1723
  {
1489
1724
  className: "fillo-option-icon",
@@ -1494,7 +1729,7 @@ function ChoiceIcon({ icon }) {
1494
1729
  strokeLinecap: "round",
1495
1730
  strokeLinejoin: "round",
1496
1731
  "aria-hidden": "true",
1497
- children: /* @__PURE__ */ jsx5("path", { d: OPTION_ICON_PATHS[icon] })
1732
+ children: /* @__PURE__ */ jsx6("path", { d: OPTION_ICON_PATHS[icon] })
1498
1733
  }
1499
1734
  );
1500
1735
  }
@@ -1508,7 +1743,7 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
1508
1743
  const isOtherValue = typeof value === "string" && value !== "" && !choice.options.some((o) => o.id === value);
1509
1744
  const [otherOn, setOtherOn] = useState5(isOtherValue);
1510
1745
  const otherActive = isOtherValue || value === "" && otherOn;
1511
- return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs5(
1746
+ return /* @__PURE__ */ jsx6(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs6(
1512
1747
  "div",
1513
1748
  {
1514
1749
  className: slotClass2("fillo-options", appearance, { slot: "options", kind: field.kind, fieldId: field.id }),
@@ -1517,7 +1752,7 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
1517
1752
  "aria-labelledby": ids.labelId,
1518
1753
  ...fieldAria(field, error, ids),
1519
1754
  children: [
1520
- options.map((opt) => /* @__PURE__ */ jsxs5(
1755
+ options.map((opt) => /* @__PURE__ */ jsxs6(
1521
1756
  "label",
1522
1757
  {
1523
1758
  "data-option": opt.id,
@@ -1529,7 +1764,7 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
1529
1764
  value === opt.id
1530
1765
  ),
1531
1766
  children: [
1532
- /* @__PURE__ */ jsx5(
1767
+ /* @__PURE__ */ jsx6(
1533
1768
  "input",
1534
1769
  {
1535
1770
  type: "radio",
@@ -1542,11 +1777,11 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
1542
1777
  }
1543
1778
  }
1544
1779
  ),
1545
- /* @__PURE__ */ jsx5(ChoiceIcon, { icon: opt.icon }),
1780
+ /* @__PURE__ */ jsx6(ChoiceIcon, { icon: opt.icon }),
1546
1781
  opt.icon && value === opt.id && // Icon-mode hides the native input, so its check state is
1547
1782
  // otherwise color-only (forfeits forced-colors' own indicator).
1548
- /* @__PURE__ */ jsx5("span", { className: "fillo-option-check", "aria-hidden": "true", children: "\u2713" }),
1549
- /* @__PURE__ */ jsx5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
1783
+ /* @__PURE__ */ jsx6("span", { className: "fillo-option-check", "aria-hidden": "true", children: "\u2713" }),
1784
+ /* @__PURE__ */ jsx6("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
1550
1785
  ]
1551
1786
  },
1552
1787
  opt.id
@@ -1556,7 +1791,7 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
1556
1791
  // option row above. The free-text input below is its own labelable
1557
1792
  // descendant, so it handles its own clicks without re-toggling the
1558
1793
  // radio (dom's proven pattern).
1559
- /* @__PURE__ */ jsxs5(
1794
+ /* @__PURE__ */ jsxs6(
1560
1795
  "label",
1561
1796
  {
1562
1797
  "data-fillo": "option",
@@ -1567,8 +1802,8 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
1567
1802
  otherActive
1568
1803
  ),
1569
1804
  children: [
1570
- /* @__PURE__ */ jsxs5("span", { className: "fillo-option-main", children: [
1571
- /* @__PURE__ */ jsx5(
1805
+ /* @__PURE__ */ jsxs6("span", { className: "fillo-option-main", children: [
1806
+ /* @__PURE__ */ jsx6(
1572
1807
  "input",
1573
1808
  {
1574
1809
  type: "radio",
@@ -1581,9 +1816,9 @@ function SingleChoice({ field, value, error, setValue, ids: providedIds }) {
1581
1816
  }
1582
1817
  }
1583
1818
  ),
1584
- /* @__PURE__ */ jsx5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
1819
+ /* @__PURE__ */ jsx6("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
1585
1820
  ] }),
1586
- otherActive && /* @__PURE__ */ jsx5(
1821
+ otherActive && /* @__PURE__ */ jsx6(
1587
1822
  "input",
1588
1823
  {
1589
1824
  type: "text",
@@ -1615,7 +1850,7 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
1615
1850
  const toggle = (id) => setValue(selected.includes(id) ? selected.filter((v) => v !== id) : [...selected, id]);
1616
1851
  const appearance = useFilloAppearance();
1617
1852
  const optionCls = (base, optionId, isSelected) => slotClass2(base, appearance, { slot: "option", kind: field.kind, fieldId: field.id, optionId, selected: isSelected });
1618
- return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs5(
1853
+ return /* @__PURE__ */ jsx6(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsxs6(
1619
1854
  "div",
1620
1855
  {
1621
1856
  className: slotClass2("fillo-options", appearance, { slot: "options", kind: field.kind, fieldId: field.id }),
@@ -1624,7 +1859,7 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
1624
1859
  "aria-labelledby": ids.labelId,
1625
1860
  ...fieldAria(field, error, ids, { required: false }),
1626
1861
  children: [
1627
- options.map((opt) => /* @__PURE__ */ jsxs5(
1862
+ options.map((opt) => /* @__PURE__ */ jsxs6(
1628
1863
  "label",
1629
1864
  {
1630
1865
  "data-option": opt.id,
@@ -1636,7 +1871,7 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
1636
1871
  selected.includes(opt.id)
1637
1872
  ),
1638
1873
  children: [
1639
- /* @__PURE__ */ jsx5(
1874
+ /* @__PURE__ */ jsx6(
1640
1875
  "input",
1641
1876
  {
1642
1877
  type: "checkbox",
@@ -1645,11 +1880,11 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
1645
1880
  onChange: () => toggle(opt.id)
1646
1881
  }
1647
1882
  ),
1648
- /* @__PURE__ */ jsx5(ChoiceIcon, { icon: opt.icon }),
1883
+ /* @__PURE__ */ jsx6(ChoiceIcon, { icon: opt.icon }),
1649
1884
  opt.icon && selected.includes(opt.id) && // Icon-mode hides the native input, so its check state is
1650
1885
  // otherwise color-only (forfeits forced-colors' own indicator).
1651
- /* @__PURE__ */ jsx5("span", { className: "fillo-option-check", "aria-hidden": "true", children: "\u2713" }),
1652
- /* @__PURE__ */ jsx5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
1886
+ /* @__PURE__ */ jsx6("span", { className: "fillo-option-check", "aria-hidden": "true", children: "\u2713" }),
1887
+ /* @__PURE__ */ jsx6("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: opt.label })
1653
1888
  ]
1654
1889
  },
1655
1890
  opt.id
@@ -1659,7 +1894,7 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
1659
1894
  // option row above. The free-text input below is its own labelable
1660
1895
  // descendant, so it handles its own clicks without re-toggling the
1661
1896
  // checkbox (dom's proven pattern).
1662
- /* @__PURE__ */ jsxs5(
1897
+ /* @__PURE__ */ jsxs6(
1663
1898
  "label",
1664
1899
  {
1665
1900
  "data-fillo": "option",
@@ -1670,8 +1905,8 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
1670
1905
  otherActive
1671
1906
  ),
1672
1907
  children: [
1673
- /* @__PURE__ */ jsxs5("span", { className: "fillo-option-main", children: [
1674
- /* @__PURE__ */ jsx5(
1908
+ /* @__PURE__ */ jsxs6("span", { className: "fillo-option-main", children: [
1909
+ /* @__PURE__ */ jsx6(
1675
1910
  "input",
1676
1911
  {
1677
1912
  type: "checkbox",
@@ -1687,9 +1922,9 @@ function MultiChoice({ field, value, error, setValue, ids: providedIds }) {
1687
1922
  }
1688
1923
  }
1689
1924
  ),
1690
- /* @__PURE__ */ jsx5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
1925
+ /* @__PURE__ */ jsx6("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: strings.other })
1691
1926
  ] }),
1692
- otherActive && /* @__PURE__ */ jsx5(
1927
+ otherActive && /* @__PURE__ */ jsx6(
1693
1928
  "input",
1694
1929
  {
1695
1930
  type: "text",
@@ -1727,9 +1962,9 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
1727
1962
  const [otherOn, setOtherOn] = useState5(isOtherValue);
1728
1963
  const otherActive = isOtherValue || value === "" && otherOn;
1729
1964
  const control = useControl("fillo-input fillo-select", field, error);
1730
- return /* @__PURE__ */ jsxs5(FieldShell, { field, error, ids, children: [
1731
- /* @__PURE__ */ jsxs5("div", { className: "fillo-select-wrap", children: [
1732
- /* @__PURE__ */ jsxs5(
1965
+ return /* @__PURE__ */ jsxs6(FieldShell, { field, error, ids, children: [
1966
+ /* @__PURE__ */ jsxs6("div", { className: "fillo-select-wrap", children: [
1967
+ /* @__PURE__ */ jsxs6(
1733
1968
  "select",
1734
1969
  {
1735
1970
  id: ids.inputId,
@@ -1746,15 +1981,15 @@ function Dropdown({ field, value, error, setValue, ids: providedIds }) {
1746
1981
  }
1747
1982
  },
1748
1983
  children: [
1749
- /* @__PURE__ */ jsx5("option", { value: "", children: field.placeholder ?? strings.choosePlaceholder }),
1750
- options.map((opt) => /* @__PURE__ */ jsx5("option", { value: opt.id, children: opt.label }, opt.id)),
1751
- choice.allowOther && /* @__PURE__ */ jsx5("option", { value: otherValue, children: `${strings.other}\u2026` })
1984
+ /* @__PURE__ */ jsx6("option", { value: "", children: field.placeholder ?? strings.choosePlaceholder }),
1985
+ options.map((opt) => /* @__PURE__ */ jsx6("option", { value: opt.id, children: opt.label }, opt.id)),
1986
+ choice.allowOther && /* @__PURE__ */ jsx6("option", { value: otherValue, children: `${strings.other}\u2026` })
1752
1987
  ]
1753
1988
  }
1754
1989
  ),
1755
- /* @__PURE__ */ jsx5("span", { className: "fillo-select-icon", "aria-hidden": "true" })
1990
+ /* @__PURE__ */ jsx6("span", { className: "fillo-select-icon", "aria-hidden": "true" })
1756
1991
  ] }),
1757
- otherActive && /* @__PURE__ */ jsx5(
1992
+ otherActive && /* @__PURE__ */ jsx6(
1758
1993
  "input",
1759
1994
  {
1760
1995
  type: "text",
@@ -1775,19 +2010,19 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
1775
2010
  const slots = useFieldSlots(field, error);
1776
2011
  const checkbox = field;
1777
2012
  if (checkbox.appearance === "toggle") {
1778
- return /* @__PURE__ */ jsxs5(
2013
+ return /* @__PURE__ */ jsxs6(
1779
2014
  "div",
1780
2015
  {
1781
2016
  ...slots.wrapperProps(
1782
2017
  `fillo-field fillo-field--checkbox fillo-field--toggle${error ? " fillo-field--error" : ""}`
1783
2018
  ),
1784
2019
  children: [
1785
- /* @__PURE__ */ jsxs5("label", { className: "fillo-toggle", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
1786
- /* @__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: [
2020
+ /* @__PURE__ */ jsxs6("label", { className: "fillo-toggle", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
2021
+ /* @__PURE__ */ jsx6("span", { className: "fillo-toggle-copy", children: /* @__PURE__ */ jsxs6("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: [
1787
2022
  field.label,
1788
- !field.required && /* @__PURE__ */ jsx5("span", { className: "fillo-optional", children: strings.optional })
2023
+ !field.required && /* @__PURE__ */ jsx6("span", { className: "fillo-optional", children: strings.optional })
1789
2024
  ] }) }),
1790
- /* @__PURE__ */ jsx5(
2025
+ /* @__PURE__ */ jsx6(
1791
2026
  "input",
1792
2027
  {
1793
2028
  id: ids.inputId,
@@ -1798,23 +2033,23 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
1798
2033
  ...fieldAria(field, error, ids)
1799
2034
  }
1800
2035
  ),
1801
- /* @__PURE__ */ jsx5("span", { className: "fillo-toggle-track", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("span", { className: "fillo-toggle-thumb" }) })
2036
+ /* @__PURE__ */ jsx6("span", { className: "fillo-toggle-track", "aria-hidden": "true", children: /* @__PURE__ */ jsx6("span", { className: "fillo-toggle-thumb" }) })
1802
2037
  ] }),
1803
- field.description && /* @__PURE__ */ jsx5("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
1804
- error && /* @__PURE__ */ jsx5("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
2038
+ field.description && /* @__PURE__ */ jsx6("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
2039
+ error && /* @__PURE__ */ jsx6("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
1805
2040
  ]
1806
2041
  }
1807
2042
  );
1808
2043
  }
1809
- return /* @__PURE__ */ jsxs5(
2044
+ return /* @__PURE__ */ jsxs6(
1810
2045
  "div",
1811
2046
  {
1812
2047
  ...slots.wrapperProps(
1813
2048
  `fillo-field fillo-field--checkbox${error ? " fillo-field--error" : ""}`
1814
2049
  ),
1815
2050
  children: [
1816
- /* @__PURE__ */ jsxs5("label", { className: "fillo-option", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
1817
- /* @__PURE__ */ jsx5(
2051
+ /* @__PURE__ */ jsxs6("label", { className: "fillo-option", "data-fillo": "option", "data-checked": value === true ? "" : void 0, children: [
2052
+ /* @__PURE__ */ jsx6(
1818
2053
  "input",
1819
2054
  {
1820
2055
  id: ids.inputId,
@@ -1825,13 +2060,13 @@ function Checkbox({ field, value, error, setValue, ids: providedIds }) {
1825
2060
  ...fieldAria(field, error, ids)
1826
2061
  }
1827
2062
  ),
1828
- /* @__PURE__ */ jsxs5("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: [
2063
+ /* @__PURE__ */ jsxs6("span", { className: slotClass2("fillo-option-label", appearance, { slot: "optionLabel", kind: field.kind, fieldId: field.id }), "data-fillo": "optionLabel", children: [
1829
2064
  field.label,
1830
- !field.required && /* @__PURE__ */ jsx5("span", { className: "fillo-optional", children: strings.optional })
2065
+ !field.required && /* @__PURE__ */ jsx6("span", { className: "fillo-optional", children: strings.optional })
1831
2066
  ] })
1832
2067
  ] }),
1833
- field.description && /* @__PURE__ */ jsx5("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
1834
- error && /* @__PURE__ */ jsx5("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
2068
+ field.description && /* @__PURE__ */ jsx6("p", { className: slots.description, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
2069
+ error && /* @__PURE__ */ jsx6("p", { className: slots.error, "data-fillo": "error", id: ids.errorId, children: error })
1835
2070
  ]
1836
2071
  }
1837
2072
  );
@@ -1863,7 +2098,7 @@ function Rating({ field, value, error, setValue, ids: providedIds }) {
1863
2098
  const tabbable = rovingIndex(steps, selected);
1864
2099
  const appearance = useFilloAppearance();
1865
2100
  const control = useControl("fillo-rating", field, error);
1866
- return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
2101
+ return /* @__PURE__ */ jsx6(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx6(
1867
2102
  "div",
1868
2103
  {
1869
2104
  id: ids.inputId,
@@ -1872,7 +2107,7 @@ function Rating({ field, value, error, setValue, ids: providedIds }) {
1872
2107
  "aria-labelledby": ids.labelId,
1873
2108
  ...fieldAria(field, error, ids),
1874
2109
  onKeyDown: (e) => radiogroupKeyDown(e, steps, selected, (n) => setValue(n)),
1875
- children: steps.map((n, i) => /* @__PURE__ */ jsx5(
2110
+ children: steps.map((n, i) => /* @__PURE__ */ jsx6(
1876
2111
  "button",
1877
2112
  {
1878
2113
  type: "button",
@@ -1918,8 +2153,8 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
1918
2153
  ].filter(Boolean).join(" ") || void 0;
1919
2154
  const appearance = useFilloAppearance();
1920
2155
  const control = useControl("fillo-scale", field, error);
1921
- return /* @__PURE__ */ jsxs5(FieldShell, { field, error, ids, children: [
1922
- /* @__PURE__ */ jsx5(
2156
+ return /* @__PURE__ */ jsxs6(FieldShell, { field, error, ids, children: [
2157
+ /* @__PURE__ */ jsx6(
1923
2158
  "div",
1924
2159
  {
1925
2160
  id: ids.inputId,
@@ -1930,7 +2165,7 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
1930
2165
  "aria-required": field.required ? true : void 0,
1931
2166
  "aria-describedby": describedBy,
1932
2167
  onKeyDown: (e) => radiogroupKeyDown(e, steps, current, (n) => setValue(n)),
1933
- children: steps.map((n, i) => /* @__PURE__ */ jsx5(
2168
+ children: steps.map((n, i) => /* @__PURE__ */ jsx6(
1934
2169
  "button",
1935
2170
  {
1936
2171
  type: "button",
@@ -1956,9 +2191,9 @@ function LinearScale({ field, value, error, setValue, ids: providedIds }) {
1956
2191
  ))
1957
2192
  }
1958
2193
  ),
1959
- hasLabels && /* @__PURE__ */ jsxs5("div", { className: "fillo-scale-labels", children: [
1960
- /* @__PURE__ */ jsx5("span", { id: minLabelId, children: scale.minLabel }),
1961
- /* @__PURE__ */ jsx5("span", { id: maxLabelId, children: scale.maxLabel })
2194
+ hasLabels && /* @__PURE__ */ jsxs6("div", { className: "fillo-scale-labels", children: [
2195
+ /* @__PURE__ */ jsx6("span", { id: minLabelId, children: scale.minLabel }),
2196
+ /* @__PURE__ */ jsx6("span", { id: maxLabelId, children: scale.maxLabel })
1962
2197
  ] })
1963
2198
  ] });
1964
2199
  }
@@ -1972,8 +2207,8 @@ function Ranking({ field, value, error, setValue, ids: providedIds }) {
1972
2207
  ...answered.filter((id) => ranking.options.some((o) => o.id === id)),
1973
2208
  ...ranking.options.map((o) => o.id).filter((id) => !answered.includes(id))
1974
2209
  ];
1975
- const pressedRef = useRef6(null);
1976
- useEffect6(() => {
2210
+ const pressedRef = useRef7(null);
2211
+ useEffect7(() => {
1977
2212
  const pressed = pressedRef.current;
1978
2213
  pressedRef.current = null;
1979
2214
  if (!pressed || !pressed.disabled) return;
@@ -1993,21 +2228,21 @@ function Ranking({ field, value, error, setValue, ids: providedIds }) {
1993
2228
  const moved = ranking.options.find((o) => o.id === id);
1994
2229
  if (moved) announce(strings.rankingPosition(moved.label, target + 1, order.length));
1995
2230
  }
1996
- return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
2231
+ return /* @__PURE__ */ jsx6(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx6(
1997
2232
  "div",
1998
2233
  {
1999
2234
  id: ids.inputId,
2000
2235
  role: "group",
2001
2236
  "aria-labelledby": ids.labelId,
2002
2237
  ...fieldAria(field, error, ids, { required: false }),
2003
- children: /* @__PURE__ */ jsx5("ol", { className: "fillo-ranking", children: order.map((id, index) => {
2238
+ children: /* @__PURE__ */ jsx6("ol", { className: "fillo-ranking", children: order.map((id, index) => {
2004
2239
  const option = ranking.options.find((o) => o.id === id);
2005
2240
  if (!option) return null;
2006
- return /* @__PURE__ */ jsxs5("li", { className: "fillo-ranking-item", children: [
2007
- /* @__PURE__ */ jsx5("span", { className: "fillo-ranking-index", children: index + 1 }),
2008
- /* @__PURE__ */ jsx5("span", { className: "fillo-ranking-label", children: option.label }),
2009
- /* @__PURE__ */ jsxs5("span", { className: "fillo-ranking-controls", children: [
2010
- /* @__PURE__ */ jsx5(
2241
+ return /* @__PURE__ */ jsxs6("li", { className: "fillo-ranking-item", children: [
2242
+ /* @__PURE__ */ jsx6("span", { className: "fillo-ranking-index", children: index + 1 }),
2243
+ /* @__PURE__ */ jsx6("span", { className: "fillo-ranking-label", children: option.label }),
2244
+ /* @__PURE__ */ jsxs6("span", { className: "fillo-ranking-controls", children: [
2245
+ /* @__PURE__ */ jsx6(
2011
2246
  "button",
2012
2247
  {
2013
2248
  type: "button",
@@ -2018,7 +2253,7 @@ function Ranking({ field, value, error, setValue, ids: providedIds }) {
2018
2253
  children: "\u2191"
2019
2254
  }
2020
2255
  ),
2021
- /* @__PURE__ */ jsx5(
2256
+ /* @__PURE__ */ jsx6(
2022
2257
  "button",
2023
2258
  {
2024
2259
  type: "button",
@@ -2039,7 +2274,7 @@ function Matrix({ field, value, error, setValue, ids: providedIds }) {
2039
2274
  const ids = useFilloFieldIds(field.id, providedIds);
2040
2275
  const matrix = field;
2041
2276
  const answers = value && typeof value === "object" && !Array.isArray(value) ? value : {};
2042
- return /* @__PURE__ */ jsx5(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx5(
2277
+ return /* @__PURE__ */ jsx6(FieldShell, { field, error, ids, children: /* @__PURE__ */ jsx6(
2043
2278
  "div",
2044
2279
  {
2045
2280
  id: ids.inputId,
@@ -2047,12 +2282,12 @@ function Matrix({ field, value, error, setValue, ids: providedIds }) {
2047
2282
  role: "group",
2048
2283
  "aria-labelledby": ids.labelId,
2049
2284
  ...fieldAria(field, error, ids, { required: false }),
2050
- children: /* @__PURE__ */ jsxs5("table", { className: "fillo-matrix", children: [
2051
- /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
2052
- /* @__PURE__ */ jsx5("td", {}),
2053
- matrix.columns.map((col) => /* @__PURE__ */ jsx5("th", { scope: "col", children: col.label }, col.id))
2285
+ children: /* @__PURE__ */ jsxs6("table", { className: "fillo-matrix", children: [
2286
+ /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsxs6("tr", { children: [
2287
+ /* @__PURE__ */ jsx6("td", {}),
2288
+ matrix.columns.map((col) => /* @__PURE__ */ jsx6("th", { scope: "col", children: col.label }, col.id))
2054
2289
  ] }) }),
2055
- /* @__PURE__ */ jsx5("tbody", { children: matrix.rows.map((row) => {
2290
+ /* @__PURE__ */ jsx6("tbody", { children: matrix.rows.map((row) => {
2056
2291
  const rowLabelId = `${ids.inputId}-${row.id}`;
2057
2292
  const answered = answers[row.id] !== void 0;
2058
2293
  const rowInvalid = Boolean(error) && field.required && !answered;
@@ -2063,17 +2298,17 @@ function Matrix({ field, value, error, setValue, ids: providedIds }) {
2063
2298
  // radiogroup lives on an inner element that aria-owns the radios
2064
2299
  // scattered across the cells (they can't be DOM-nested inside a
2065
2300
  // single element within a table row).
2066
- /* @__PURE__ */ jsxs5("tr", { children: [
2067
- /* @__PURE__ */ jsx5("th", { scope: "row", children: /* @__PURE__ */ jsx5(
2301
+ /* @__PURE__ */ jsxs6("tr", { children: [
2302
+ /* @__PURE__ */ jsx6("th", { scope: "row", children: /* @__PURE__ */ jsx6(
2068
2303
  "div",
2069
2304
  {
2070
2305
  role: "radiogroup",
2071
2306
  "aria-labelledby": `${ids.labelId} ${rowLabelId}`,
2072
2307
  "aria-owns": matrix.columns.map(radioId).join(" "),
2073
- children: /* @__PURE__ */ jsx5("span", { id: rowLabelId, children: row.label })
2308
+ children: /* @__PURE__ */ jsx6("span", { id: rowLabelId, children: row.label })
2074
2309
  }
2075
2310
  ) }),
2076
- matrix.columns.map((col) => /* @__PURE__ */ jsx5("td", { "data-label": col.label, children: /* @__PURE__ */ jsx5("label", { className: "fillo-matrix-cell", children: /* @__PURE__ */ jsx5(
2311
+ matrix.columns.map((col) => /* @__PURE__ */ jsx6("td", { "data-label": col.label, children: /* @__PURE__ */ jsx6("label", { className: "fillo-matrix-cell", children: /* @__PURE__ */ jsx6(
2077
2312
  "input",
2078
2313
  {
2079
2314
  id: radioId(col),
@@ -2107,7 +2342,7 @@ function Calculated({ field, value, ids: providedIds }) {
2107
2342
  const labelCls = useSlotClass("fillo-label", { slot: "label", ...state });
2108
2343
  const descriptionCls = useSlotClass("fillo-description", { slot: "fieldDescription", ...state });
2109
2344
  const answered = value !== void 0 && value !== null && value !== "";
2110
- return /* @__PURE__ */ jsxs5(
2345
+ return /* @__PURE__ */ jsxs6(
2111
2346
  "div",
2112
2347
  {
2113
2348
  className: rootCls,
@@ -2115,9 +2350,9 @@ function Calculated({ field, value, ids: providedIds }) {
2115
2350
  "data-field": field.id,
2116
2351
  "data-kind": field.kind,
2117
2352
  children: [
2118
- /* @__PURE__ */ jsx5("label", { className: labelCls, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: field.label }),
2119
- field.description && /* @__PURE__ */ jsx5("p", { className: descriptionCls, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
2120
- /* @__PURE__ */ jsx5(
2353
+ /* @__PURE__ */ jsx6("label", { className: labelCls, "data-fillo": "label", id: ids.labelId, htmlFor: ids.inputId, children: field.label }),
2354
+ field.description && /* @__PURE__ */ jsx6("p", { className: descriptionCls, "data-fillo": "fieldDescription", id: ids.descriptionId, children: field.description }),
2355
+ /* @__PURE__ */ jsx6(
2121
2356
  "output",
2122
2357
  {
2123
2358
  id: ids.inputId,
@@ -2149,16 +2384,17 @@ var DEFAULT_COMPONENTS = {
2149
2384
  signature: SignatureField,
2150
2385
  file_upload: FileUploadField,
2151
2386
  hidden: Hidden,
2152
- calculated: Calculated
2387
+ calculated: Calculated,
2388
+ repeating_group: RepeatingGroup
2153
2389
  };
2154
2390
  function ContentRenderer({ block }) {
2155
2391
  switch (block.kind) {
2156
2392
  case "heading":
2157
- return /* @__PURE__ */ jsx5("h3", { className: "fillo-heading", children: block.text });
2393
+ return /* @__PURE__ */ jsx6("h3", { className: "fillo-heading", children: block.text });
2158
2394
  case "paragraph":
2159
- return /* @__PURE__ */ jsx5("p", { className: "fillo-paragraph", children: block.text });
2395
+ return /* @__PURE__ */ jsx6("p", { className: "fillo-paragraph", children: block.text });
2160
2396
  case "divider":
2161
- return /* @__PURE__ */ jsx5("hr", { className: "fillo-divider" });
2397
+ return /* @__PURE__ */ jsx6("hr", { className: "fillo-divider" });
2162
2398
  }
2163
2399
  }
2164
2400
  function blockHasPiping(block) {
@@ -2197,7 +2433,7 @@ function BlockRendererInner({ block, api, components, customComponents }) {
2197
2433
  block = pipeBlock(block, api.data, api.form);
2198
2434
  const ids = useFilloFieldIds(block.id);
2199
2435
  const strings = useStrings();
2200
- if (!isField(block)) return /* @__PURE__ */ jsx5(ContentRenderer, { block });
2436
+ if (!isField(block)) return /* @__PURE__ */ jsx6(ContentRenderer, { block });
2201
2437
  let Component;
2202
2438
  if (block.kind === "custom") {
2203
2439
  Component = customComponents?.[block.component] ?? components?.custom;
@@ -2214,8 +2450,8 @@ function BlockRendererInner({ block, api, components, customComponents }) {
2214
2450
  Component = components?.[block.kind] ?? DEFAULT_COMPONENTS[block.kind];
2215
2451
  }
2216
2452
  const rawError = api.errors[block.id];
2217
- const error = rawError === REQUIRED_FIELD_MESSAGE ? strings.required : rawError;
2218
- return /* @__PURE__ */ jsx5(
2453
+ const error = rawError === REQUIRED_FIELD_MESSAGE ? requiredFieldMessage(block, strings) : rawError;
2454
+ return /* @__PURE__ */ jsx6(
2219
2455
  Component,
2220
2456
  {
2221
2457
  field: block,
@@ -2245,14 +2481,14 @@ function FormField({
2245
2481
  customComponents
2246
2482
  }) {
2247
2483
  const api = useFillo();
2248
- const visibleIds = useMemo4(
2484
+ const visibleIds = useMemo5(
2249
2485
  () => new Set(visibleFields(api.form, api.data).map((f) => f.id)),
2250
2486
  [api.form, api.data]
2251
2487
  );
2252
2488
  const block = api.form.pages.flatMap((p) => p.blocks).find((b) => b.id === id);
2253
2489
  if (!block) return null;
2254
2490
  if (isField(block) && !visibleIds.has(block.id)) return null;
2255
- return /* @__PURE__ */ jsx5(
2491
+ return /* @__PURE__ */ jsx6(
2256
2492
  BlockRenderer,
2257
2493
  {
2258
2494
  block,
@@ -2264,8 +2500,8 @@ function FormField({
2264
2500
  }
2265
2501
 
2266
2502
  // src/turnstile.tsx
2267
- import { useEffect as useEffect7, useRef as useRef7 } from "react";
2268
- import { jsx as jsx6 } from "react/jsx-runtime";
2503
+ import { useEffect as useEffect8, useRef as useRef8 } from "react";
2504
+ import { jsx as jsx7 } from "react/jsx-runtime";
2269
2505
  var SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
2270
2506
  var SCRIPT_ID = "fillo-turnstile-script";
2271
2507
  var scriptPromise = null;
@@ -2323,15 +2559,15 @@ function TurnstileWidget({
2323
2559
  registerReset,
2324
2560
  className
2325
2561
  }) {
2326
- const containerRef = useRef7(null);
2327
- const widgetIdRef = useRef7(null);
2328
- const onTokenRef = useRef7(onToken);
2562
+ const containerRef = useRef8(null);
2563
+ const widgetIdRef = useRef8(null);
2564
+ const onTokenRef = useRef8(onToken);
2329
2565
  onTokenRef.current = onToken;
2330
- const onErrorRef = useRef7(onError);
2566
+ const onErrorRef = useRef8(onError);
2331
2567
  onErrorRef.current = onError;
2332
- const registerResetRef = useRef7(registerReset);
2568
+ const registerResetRef = useRef8(registerReset);
2333
2569
  registerResetRef.current = registerReset;
2334
- useEffect7(() => {
2570
+ useEffect8(() => {
2335
2571
  let cancelled = false;
2336
2572
  loadTurnstileScript().then(() => {
2337
2573
  if (cancelled || !containerRef.current) return;
@@ -2372,7 +2608,7 @@ function TurnstileWidget({
2372
2608
  onTokenRef.current(void 0);
2373
2609
  };
2374
2610
  }, [siteKey]);
2375
- return /* @__PURE__ */ jsx6(
2611
+ return /* @__PURE__ */ jsx7(
2376
2612
  "div",
2377
2613
  {
2378
2614
  ref: containerRef,
@@ -2383,7 +2619,7 @@ function TurnstileWidget({
2383
2619
  }
2384
2620
 
2385
2621
  // src/FilloForm.tsx
2386
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2622
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2387
2623
  function themeProps(theme) {
2388
2624
  const safeTheme = resolveThemeAppearance(normalizeFormTheme(theme));
2389
2625
  if (!safeTheme) return {};
@@ -2405,25 +2641,22 @@ function badgeUrl(href) {
2405
2641
  url.searchParams.set("utm_source", "form_badge");
2406
2642
  return url.toString();
2407
2643
  }
2408
- var FOCUSABLE_SELECTOR = 'input:not([disabled]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])';
2644
+ var FOCUSABLE_SELECTOR2 = 'input:not([disabled]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])';
2409
2645
  function resolveFocusable(el) {
2410
2646
  if (!el) return null;
2411
- return el.matches(FOCUSABLE_SELECTOR) ? el : el.querySelector(FOCUSABLE_SELECTOR);
2647
+ return el.matches(FOCUSABLE_SELECTOR2) ? el : el.querySelector(FOCUSABLE_SELECTOR2);
2412
2648
  }
2413
2649
  function nextFrame(run) {
2414
2650
  if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
2415
2651
  else setTimeout(run, 0);
2416
2652
  }
2417
- function focusErrorSummary(formEl) {
2653
+ function focusFirstInvalid(formEl) {
2418
2654
  if (!formEl || typeof window === "undefined") return;
2419
2655
  nextFrame(() => {
2420
- formEl.querySelector(".fillo-error-summary")?.focus();
2656
+ const invalid = formEl.querySelector('[aria-invalid="true"]');
2657
+ resolveFocusable(invalid)?.focus();
2421
2658
  });
2422
2659
  }
2423
- function focusField(inputId) {
2424
- if (typeof document === "undefined") return;
2425
- resolveFocusable(document.getElementById(inputId))?.focus();
2426
- }
2427
2660
  var HP_STYLE = {
2428
2661
  position: "absolute",
2429
2662
  left: "-9999px",
@@ -2443,7 +2676,7 @@ function assertHpHidden(el) {
2443
2676
  function FilloForm(props) {
2444
2677
  const { form, formId } = props;
2445
2678
  const renderOnly = props.renderOnly === true || props.skipValidation === true;
2446
- const client = useMemo5(
2679
+ const client = useMemo6(
2447
2680
  () => renderOnly ? void 0 : props.client ?? (formId ? createClient() : void 0),
2448
2681
  [renderOnly, props.client, formId]
2449
2682
  );
@@ -2480,11 +2713,11 @@ function FilloForm(props) {
2480
2713
  });
2481
2714
  const renderResolvedSnapshot = Boolean(resolvedSchema && !devChrome);
2482
2715
  const inlineSchema = codeForm ? renderResolvedSnapshot ? resolvedSchema : codeForm.schema : form;
2483
- const onErrorRef = useRef8(onError);
2484
- useEffect8(() => {
2716
+ const onErrorRef = useRef9(onError);
2717
+ useEffect9(() => {
2485
2718
  onErrorRef.current = onError;
2486
2719
  });
2487
- const targetError = useMemo5(
2720
+ const targetError = useMemo6(
2488
2721
  () => form && !formId && !renderOnly && (!isCodeForm2(form) || !props.client) ? new FilloError2(
2489
2722
  "This form has no Fillo target. Pass formId with the schema, use a defineForm() value with client, or set renderOnly for a non-submitting preview.",
2490
2723
  422,
@@ -2493,11 +2726,11 @@ function FilloForm(props) {
2493
2726
  ) : null,
2494
2727
  [form, formId, props.client, renderOnly]
2495
2728
  );
2496
- useEffect8(() => {
2729
+ useEffect9(() => {
2497
2730
  if (targetError) onErrorRef.current?.(targetError);
2498
2731
  }, [targetError]);
2499
2732
  const needsFetch = !inlineSchema && Boolean(client && formId);
2500
- useEffect8(() => {
2733
+ useEffect9(() => {
2501
2734
  if (!needsFetch || !client || !formId) {
2502
2735
  setLoaded(null);
2503
2736
  setLoadFailure(null);
@@ -2534,7 +2767,7 @@ function FilloForm(props) {
2534
2767
  const currentLoaded = needsFetch && loaded?.formId === formId ? loaded : null;
2535
2768
  const loadError = needsFetch && loadFailure && loadFailure.formId === formId ? loadFailure.error : null;
2536
2769
  const rawSchema = inlineSchema ?? currentLoaded?.schema ?? null;
2537
- const normalized = useMemo5(
2770
+ const normalized = useMemo6(
2538
2771
  () => rawSchema ? normalizeFormSchema(rawSchema) : null,
2539
2772
  [rawSchema]
2540
2773
  );
@@ -2542,8 +2775,8 @@ function FilloForm(props) {
2542
2775
  const resolvedCodeTheme = renderResolvedSnapshot ? resolvedTheme ?? void 0 : codeForm?.theme;
2543
2776
  const theme = props.appearance?.theme ?? props.theme ?? resolvedCodeTheme ?? currentLoaded?.theme ?? void 0;
2544
2777
  if (targetError) {
2545
- if (props.renderError) return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(targetError) });
2546
- return /* @__PURE__ */ jsx7(
2778
+ if (props.renderError) return /* @__PURE__ */ jsx8(Fragment3, { children: props.renderError(targetError) });
2779
+ return /* @__PURE__ */ jsx8(
2547
2780
  "div",
2548
2781
  {
2549
2782
  className: `fillo-form fillo-form--error ${props.className ?? ""}`,
@@ -2558,15 +2791,15 @@ function FilloForm(props) {
2558
2791
  }
2559
2792
  if (normalized && !normalized.ok) {
2560
2793
  const error = new FilloError2(`This form could not be rendered: ${normalized.error}`, 422);
2561
- if (props.renderError) return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(error) });
2562
- return /* @__PURE__ */ jsx7("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: strings.renderFailed });
2794
+ if (props.renderError) return /* @__PURE__ */ jsx8(Fragment3, { children: props.renderError(error) });
2795
+ return /* @__PURE__ */ jsx8("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: strings.renderFailed });
2563
2796
  }
2564
2797
  if (loadError) {
2565
2798
  if (props.renderError) {
2566
- return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(loadError) });
2799
+ return /* @__PURE__ */ jsx8(Fragment3, { children: props.renderError(loadError) });
2567
2800
  }
2568
2801
  const message = loadError.status === 404 ? strings.loadFailedNotFound : loadError.status === 0 ? strings.loadFailedNetwork : strings.loadFailed;
2569
- return /* @__PURE__ */ jsx7("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: message });
2802
+ return /* @__PURE__ */ jsx8("div", { className: `fillo-form fillo-form--error ${props.className ?? ""}`, children: message });
2570
2803
  }
2571
2804
  if (currentLoaded?.accepting === false && schema) {
2572
2805
  const variant = overlayVariant(currentLoaded.acceptingReason);
@@ -2582,9 +2815,9 @@ function FilloForm(props) {
2582
2815
  void 0,
2583
2816
  currentLoaded.acceptingReason
2584
2817
  );
2585
- return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(error) });
2818
+ return /* @__PURE__ */ jsx8(Fragment3, { children: props.renderError(error) });
2586
2819
  }
2587
- return /* @__PURE__ */ jsx7(
2820
+ return /* @__PURE__ */ jsx8(
2588
2821
  NotOpenOverlay,
2589
2822
  {
2590
2823
  ...props,
@@ -2596,21 +2829,21 @@ function FilloForm(props) {
2596
2829
  );
2597
2830
  }
2598
2831
  if (currentLoaded?.closed) {
2599
- return /* @__PURE__ */ jsx7(
2832
+ return /* @__PURE__ */ jsx8(
2600
2833
  "div",
2601
2834
  {
2602
2835
  className: `fillo-form fillo-form--closed ${props.className ?? ""}`,
2603
2836
  ...themeProps(theme),
2604
- children: /* @__PURE__ */ jsx7("p", { className: "fillo-closed", children: strings.closed })
2837
+ children: /* @__PURE__ */ jsx8("p", { className: "fillo-closed", children: strings.closed })
2605
2838
  }
2606
2839
  );
2607
2840
  }
2608
2841
  const canonicalSyncError = codeForm && syncError && !props.skipValidation ? syncError : null;
2609
2842
  if (canonicalSyncError && props.renderError) {
2610
- return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(canonicalSyncError) });
2843
+ return /* @__PURE__ */ jsx8(Fragment3, { children: props.renderError(canonicalSyncError) });
2611
2844
  }
2612
2845
  if (canonicalSyncError && !devChrome) {
2613
- return /* @__PURE__ */ jsx7(
2846
+ return /* @__PURE__ */ jsx8(
2614
2847
  "div",
2615
2848
  {
2616
2849
  className: `fillo-form fillo-form--error ${props.className ?? ""}`,
@@ -2632,9 +2865,9 @@ function FilloForm(props) {
2632
2865
  void 0,
2633
2866
  syncedAcceptingReason
2634
2867
  ) : new FilloError2("This form isn't published yet.", 403);
2635
- if (props.renderError) return /* @__PURE__ */ jsx7(Fragment3, { children: props.renderError(error) });
2868
+ if (props.renderError) return /* @__PURE__ */ jsx8(Fragment3, { children: props.renderError(error) });
2636
2869
  if (schema) {
2637
- return /* @__PURE__ */ jsx7(
2870
+ return /* @__PURE__ */ jsx8(
2638
2871
  NotOpenOverlay,
2639
2872
  {
2640
2873
  ...props,
@@ -2645,27 +2878,27 @@ function FilloForm(props) {
2645
2878
  }
2646
2879
  );
2647
2880
  }
2648
- return /* @__PURE__ */ jsx7(
2881
+ return /* @__PURE__ */ jsx8(
2649
2882
  "div",
2650
2883
  {
2651
2884
  className: `fillo-form fillo-form--closed ${props.className ?? ""}`,
2652
2885
  ...themeProps(theme),
2653
- children: /* @__PURE__ */ jsx7("p", { className: "fillo-closed", children: variant === "closed" ? strings.closed : strings.notLive })
2886
+ children: /* @__PURE__ */ jsx8("p", { className: "fillo-closed", children: variant === "closed" ? strings.closed : strings.notLive })
2654
2887
  }
2655
2888
  );
2656
2889
  }
2657
2890
  const awaitingCanonical = Boolean(codeForm && codeFormSyncing && !props.skipValidation);
2658
2891
  if (!devChrome && awaitingCanonical || !schema) {
2659
- return /* @__PURE__ */ jsxs6(
2892
+ return /* @__PURE__ */ jsxs7(
2660
2893
  "div",
2661
2894
  {
2662
2895
  className: `fillo-form fillo-form--loading ${props.className ?? ""}`,
2663
2896
  "aria-busy": "true",
2664
2897
  ...themeProps(theme),
2665
2898
  children: [
2666
- /* @__PURE__ */ jsx7("div", { className: "fillo-skeleton" }),
2667
- /* @__PURE__ */ jsx7("div", { className: "fillo-skeleton" }),
2668
- /* @__PURE__ */ jsx7("div", { className: "fillo-skeleton fillo-skeleton--short" })
2899
+ /* @__PURE__ */ jsx8("div", { className: "fillo-skeleton" }),
2900
+ /* @__PURE__ */ jsx8("div", { className: "fillo-skeleton" }),
2901
+ /* @__PURE__ */ jsx8("div", { className: "fillo-skeleton fillo-skeleton--short" })
2669
2902
  ]
2670
2903
  }
2671
2904
  );
@@ -2673,7 +2906,7 @@ function FilloForm(props) {
2673
2906
  const branding = codeForm ? syncedBranding : currentLoaded?.branding;
2674
2907
  const poweredBy = branding?.poweredBy ?? true;
2675
2908
  const challenge = props.challenge ?? (codeForm ? syncedChallenge : currentLoaded?.challenge) ?? void 0;
2676
- return /* @__PURE__ */ jsx7(
2909
+ return /* @__PURE__ */ jsx8(
2677
2910
  ResolvedForm,
2678
2911
  {
2679
2912
  ...props,
@@ -2698,7 +2931,7 @@ function FilloForm(props) {
2698
2931
  );
2699
2932
  }
2700
2933
  function PoweredBy({ label, href }) {
2701
- return /* @__PURE__ */ jsx7("a", { className: "fillo-powered", href: badgeUrl(href), target: "_blank", rel: "noopener noreferrer", children: label ?? "Powered by Fillo" });
2934
+ return /* @__PURE__ */ jsx8("a", { className: "fillo-powered", href: badgeUrl(href), target: "_blank", rel: "noopener noreferrer", children: label ?? "Powered by Fillo" });
2702
2935
  }
2703
2936
  function overlayVariant(reason) {
2704
2937
  return reason === "expired" || reason === "capped" || reason === "storage_full" ? "closed" : "notOpen";
@@ -2706,7 +2939,7 @@ function overlayVariant(reason) {
2706
2939
  function NotOpenOverlay(props) {
2707
2940
  const strings = resolveStrings(props.strings);
2708
2941
  const closedFlavor = props.variant === "closed";
2709
- return /* @__PURE__ */ jsxs6(
2942
+ return /* @__PURE__ */ jsxs7(
2710
2943
  "div",
2711
2944
  {
2712
2945
  className: `fillo-form fillo-form--not-open ${props.className ?? ""}`,
@@ -2714,13 +2947,13 @@ function NotOpenOverlay(props) {
2714
2947
  "data-state": "closed",
2715
2948
  ...themeProps(props.theme),
2716
2949
  children: [
2717
- /* @__PURE__ */ jsx7(
2950
+ /* @__PURE__ */ jsx8(
2718
2951
  "div",
2719
2952
  {
2720
2953
  className: "fillo-not-open-preview",
2721
2954
  "aria-hidden": "true",
2722
2955
  ref: (node) => node?.setAttribute("inert", ""),
2723
- children: /* @__PURE__ */ jsx7(
2956
+ children: /* @__PURE__ */ jsx8(
2724
2957
  ResolvedForm,
2725
2958
  {
2726
2959
  schema: props.schema,
@@ -2736,11 +2969,11 @@ function NotOpenOverlay(props) {
2736
2969
  )
2737
2970
  }
2738
2971
  ),
2739
- /* @__PURE__ */ jsxs6("div", { className: "fillo-not-open-card", "data-fillo": "not-open-card", role: "status", children: [
2740
- /* @__PURE__ */ jsx7("h2", { className: "fillo-not-open-title", children: closedFlavor ? strings.closedTitle : strings.notOpenTitle }),
2741
- /* @__PURE__ */ jsx7("p", { className: "fillo-not-open-body", children: closedFlavor ? strings.closed : strings.notOpenBody })
2972
+ /* @__PURE__ */ jsxs7("div", { className: "fillo-not-open-card", "data-fillo": "not-open-card", role: "status", children: [
2973
+ /* @__PURE__ */ jsx8("h2", { className: "fillo-not-open-title", children: closedFlavor ? strings.closedTitle : strings.notOpenTitle }),
2974
+ /* @__PURE__ */ jsx8("p", { className: "fillo-not-open-body", children: closedFlavor ? strings.closed : strings.notOpenBody })
2742
2975
  ] }),
2743
- (props.branding?.poweredBy ?? true) && /* @__PURE__ */ jsx7(PoweredBy, { label: props.branding?.label, href: props.branding?.href })
2976
+ (props.branding?.poweredBy ?? true) && /* @__PURE__ */ jsx8(PoweredBy, { label: props.branding?.label, href: props.branding?.href })
2744
2977
  ]
2745
2978
  }
2746
2979
  );
@@ -2757,26 +2990,26 @@ function ResolvedForm(props) {
2757
2990
  const instanceId = useId2().replace(/:/g, "");
2758
2991
  const devEnv = useIsDevEnv();
2759
2992
  const devChrome = !displayOnly && (props.preview === true || devEnv);
2760
- const hpRef = useRef8(null);
2993
+ const hpRef = useRef9(null);
2761
2994
  const codeForm = isCodeForm2(props.form) ? props.form : null;
2762
2995
  const client = props.client;
2763
2996
  const challenge = renderOnly ? void 0 : props.challenge;
2764
2997
  const challengeRequired = Boolean(challenge);
2765
- const challengeTokenRef = useRef8(void 0);
2998
+ const challengeTokenRef = useRef9(void 0);
2766
2999
  const [resyncWarning, setResyncWarning] = useState6(null);
2767
3000
  const [resolutionFailureCode, setResolutionFailureCode] = useState6(null);
2768
3001
  const [challengeToken, setChallengeToken] = useState6(void 0);
2769
3002
  const [challengeError, setChallengeError] = useState6(false);
2770
- const resetChallengeRef = useRef8(void 0);
2771
- const handleChallengeToken = useCallback((token) => {
3003
+ const resetChallengeRef = useRef9(void 0);
3004
+ const handleChallengeToken = useCallback2((token) => {
2772
3005
  challengeTokenRef.current = token;
2773
3006
  setChallengeToken(token);
2774
3007
  if (token) setChallengeError(false);
2775
3008
  }, []);
2776
- const registerChallengeReset = useCallback((reset) => {
3009
+ const registerChallengeReset = useCallback2((reset) => {
2777
3010
  resetChallengeRef.current = reset;
2778
3011
  }, []);
2779
- const resolveFormId = useMemo5(
3012
+ const resolveFormId = useMemo6(
2780
3013
  () => codeForm && client?.key ? (
2781
3014
  // bypassCache: a stale cached formId must not be trusted at submit time.
2782
3015
  async () => {
@@ -2874,8 +3107,8 @@ function ResolvedForm(props) {
2874
3107
  });
2875
3108
  useDraftFlush(api, !displayOnly && !renderOnly);
2876
3109
  const strings = resolveStrings(props.strings);
2877
- const announceRef = useRef8(null);
2878
- const announce = useCallback((text) => {
3110
+ const announceRef = useRef9(null);
3111
+ const announce = useCallback2((text) => {
2879
3112
  const el = announceRef.current;
2880
3113
  if (!el) return;
2881
3114
  if (el.textContent === text) {
@@ -2887,20 +3120,20 @@ function ResolvedForm(props) {
2887
3120
  }
2888
3121
  el.textContent = text;
2889
3122
  }, []);
2890
- useEffect8(() => {
3123
+ useEffect9(() => {
2891
3124
  if (api.status === "submitting") announce(strings.submittingAnnouncement);
2892
3125
  }, [api.status, announce, strings.submittingAnnouncement]);
2893
3126
  const resuming = api.resumedDraft || api.editingPrevious;
2894
3127
  const resumeAnnouncement = api.editingPrevious ? strings.editNotice : strings.resumeNotice;
2895
- useEffect8(() => {
3128
+ useEffect9(() => {
2896
3129
  if (resuming) announce(resumeAnnouncement);
2897
3130
  }, [resuming, resumeAnnouncement, announce]);
2898
- const themed = useMemo5(() => themeProps(theme), [theme]);
3131
+ const themed = useMemo6(() => themeProps(theme), [theme]);
2899
3132
  const warningCode = resyncWarning ? resyncWarning.code : props.syncWarningCode;
2900
3133
  const warningUrl = resyncWarning ? resyncWarning.url : props.syncWarningUrl;
2901
3134
  const uploadsAvailable = resyncWarning ? resyncWarning.uploadsAvailable : props.uploadsAvailable;
2902
3135
  const uploadFileSizeLimitMb = resyncWarning ? resyncWarning.uploadFileSizeLimitMb : props.uploadFileSizeLimitMb;
2903
- const chromeValue = useMemo5(
3136
+ const chromeValue = useMemo6(
2904
3137
  () => ({
2905
3138
  devChrome,
2906
3139
  renderOnly,
@@ -2914,18 +3147,18 @@ function ResolvedForm(props) {
2914
3147
  const settings = schema.settings;
2915
3148
  const submitted = api.status === "submitted";
2916
3149
  const liveSubmit = submitted && !api.restoredSubmission;
2917
- useEffect8(() => {
3150
+ useEffect9(() => {
2918
3151
  if (!liveSubmit || !settings.redirectUrl || typeof window === "undefined") return;
2919
3152
  const url = safeHttpUrl(settings.redirectUrl);
2920
3153
  if (url) window.location.assign(url);
2921
3154
  }, [liveSubmit, settings.redirectUrl]);
2922
- const successRef = useRef8(null);
2923
- useEffect8(() => {
3155
+ const successRef = useRef9(null);
3156
+ useEffect9(() => {
2924
3157
  if (liveSubmit) successRef.current?.focus();
2925
3158
  }, [liveSubmit]);
2926
- const blocksRef = useRef8(null);
2927
- const prevPageIndex = useRef8(api.pageIndex);
2928
- useEffect8(() => {
3159
+ const blocksRef = useRef9(null);
3160
+ const prevPageIndex = useRef9(api.pageIndex);
3161
+ useEffect9(() => {
2929
3162
  if (prevPageIndex.current === api.pageIndex) return;
2930
3163
  prevPageIndex.current = api.pageIndex;
2931
3164
  const container = blocksRef.current?.parentElement;
@@ -2937,7 +3170,7 @@ function ResolvedForm(props) {
2937
3170
  }, [api.pageIndex]);
2938
3171
  const appearance = props.appearance;
2939
3172
  if (api.status === "submitted") {
2940
- return /* @__PURE__ */ jsxs6(
3173
+ return /* @__PURE__ */ jsxs7(
2941
3174
  "div",
2942
3175
  {
2943
3176
  ref: successRef,
@@ -2954,7 +3187,7 @@ function ResolvedForm(props) {
2954
3187
  "data-state": "submitted",
2955
3188
  ...themed,
2956
3189
  children: [
2957
- props.renderSuccess ? props.renderSuccess() : /* @__PURE__ */ jsxs6(
3190
+ props.renderSuccess ? props.renderSuccess() : /* @__PURE__ */ jsxs7(
2958
3191
  "div",
2959
3192
  {
2960
3193
  className: slotClass3("fillo-success", appearance, {
@@ -2963,13 +3196,13 @@ function ResolvedForm(props) {
2963
3196
  }),
2964
3197
  "data-fillo": "success",
2965
3198
  children: [
2966
- /* @__PURE__ */ jsx7("div", { className: "fillo-success-mark", "aria-hidden": "true" }),
2967
- /* @__PURE__ */ jsx7("h2", { className: "fillo-success-title", children: settings.successTitle ?? strings.successTitle }),
2968
- /* @__PURE__ */ jsx7("p", { className: "fillo-success-message", children: api.duplicateSubmission ? strings.alreadyAnswered : settings.successMessage ?? strings.successMessage })
3199
+ /* @__PURE__ */ jsx8("div", { className: "fillo-success-mark", "aria-hidden": "true" }),
3200
+ /* @__PURE__ */ jsx8("h2", { className: "fillo-success-title", children: settings.successTitle ?? strings.successTitle }),
3201
+ /* @__PURE__ */ jsx8("p", { className: "fillo-success-message", children: api.duplicateSubmission ? strings.alreadyAnswered : settings.successMessage ?? strings.successMessage })
2969
3202
  ]
2970
3203
  }
2971
3204
  ),
2972
- props.poweredBy && /* @__PURE__ */ jsx7(PoweredBy, { label: props.brandLabel, href: props.brandHref })
3205
+ props.poweredBy && /* @__PURE__ */ jsx8(PoweredBy, { label: props.brandLabel, href: props.brandHref })
2973
3206
  ]
2974
3207
  }
2975
3208
  );
@@ -2986,23 +3219,12 @@ function ResolvedForm(props) {
2986
3219
  const progressPos = progressSeq.indexOf(api.page.id);
2987
3220
  const progressStep = progressPos >= 0 ? progressPos + 1 : Math.min(api.pageIndex + 1, progressTotal);
2988
3221
  const submitFixUrl = devChrome && resolutionFailureCode === "form_not_published" && warningCode === "storage_required" ? safeHttpUrl(warningUrl) : null;
2989
- const invalidFields = [];
2990
- for (const block of api.blocks) {
2991
- if (!isField2(block)) continue;
2992
- const raw = api.errors[block.id];
2993
- if (!raw) continue;
2994
- invalidFields.push({
2995
- field: block,
2996
- error: raw === REQUIRED_FIELD_MESSAGE2 ? strings.required : raw,
2997
- inputId: createFilloFieldIds(instanceId, block.id).inputId
2998
- });
2999
- }
3000
3222
  const rootClass = slotClass3(`fillo-form ${props.className ?? ""}`, appearance, {
3001
3223
  slot: "root",
3002
3224
  status: api.status
3003
3225
  });
3004
- const formBody = /* @__PURE__ */ jsxs6(Fragment3, { children: [
3005
- /* @__PURE__ */ jsx7(
3226
+ const formBody = /* @__PURE__ */ jsxs7(Fragment3, { children: [
3227
+ /* @__PURE__ */ jsx8(
3006
3228
  "span",
3007
3229
  {
3008
3230
  ref: announceRef,
@@ -3012,7 +3234,7 @@ function ResolvedForm(props) {
3012
3234
  "data-fillo": "announce"
3013
3235
  }
3014
3236
  ),
3015
- devChrome && /* @__PURE__ */ jsx7(
3237
+ devChrome && /* @__PURE__ */ jsx8(
3016
3238
  DevChrome,
3017
3239
  {
3018
3240
  preview: props.preview,
@@ -3024,18 +3246,18 @@ function ResolvedForm(props) {
3024
3246
  noClient: !props.client && !props.skipValidation && !renderOnly
3025
3247
  }
3026
3248
  ),
3027
- resuming && /* @__PURE__ */ jsxs6(
3249
+ resuming && /* @__PURE__ */ jsxs7(
3028
3250
  "div",
3029
3251
  {
3030
3252
  className: slotClass3("fillo-resume", appearance, { slot: "resume" }),
3031
3253
  "data-fillo": "resume",
3032
3254
  children: [
3033
- /* @__PURE__ */ jsx7("span", { className: "fillo-resume-text", children: api.editingPrevious ? strings.editNotice : strings.resumeNotice }),
3034
- /* @__PURE__ */ jsx7("button", { type: "button", className: "fillo-resume-clear", onClick: api.resetDraft, children: strings.resumeStartOver })
3255
+ /* @__PURE__ */ jsx8("span", { className: "fillo-resume-text", children: api.editingPrevious ? strings.editNotice : strings.resumeNotice }),
3256
+ /* @__PURE__ */ jsx8("button", { type: "button", className: "fillo-resume-clear", onClick: api.resetDraft, children: strings.resumeStartOver })
3035
3257
  ]
3036
3258
  }
3037
3259
  ),
3038
- api.resumeLinkFailed && !api.resumedDraft && !api.editingPrevious && /* @__PURE__ */ jsx7(
3260
+ api.resumeLinkFailed && !api.resumedDraft && !api.editingPrevious && /* @__PURE__ */ jsx8(
3039
3261
  "div",
3040
3262
  {
3041
3263
  className: slotClass3("fillo-resume fillo-resume--expired", appearance, {
@@ -3043,10 +3265,10 @@ function ResolvedForm(props) {
3043
3265
  }),
3044
3266
  "data-fillo": "resume",
3045
3267
  role: "status",
3046
- children: /* @__PURE__ */ jsx7("span", { className: "fillo-resume-text", children: strings.resumeLinkExpired })
3268
+ children: /* @__PURE__ */ jsx8("span", { className: "fillo-resume-text", children: strings.resumeLinkExpired })
3047
3269
  }
3048
3270
  ),
3049
- multiPage && settings.showProgress !== false && /* @__PURE__ */ jsx7(
3271
+ multiPage && settings.showProgress !== false && /* @__PURE__ */ jsx8(
3050
3272
  "div",
3051
3273
  {
3052
3274
  className: slotClass3("fillo-progress-track", appearance, { slot: "progress" }),
@@ -3056,7 +3278,7 @@ function ResolvedForm(props) {
3056
3278
  "aria-valuemin": 1,
3057
3279
  "aria-valuemax": progressTotal,
3058
3280
  "aria-valuenow": progressStep,
3059
- children: /* @__PURE__ */ jsx7(
3281
+ children: /* @__PURE__ */ jsx8(
3060
3282
  "div",
3061
3283
  {
3062
3284
  className: slotClass3("fillo-progress-fill", appearance, { slot: "progressFill" }),
@@ -3068,13 +3290,13 @@ function ResolvedForm(props) {
3068
3290
  )
3069
3291
  }
3070
3292
  ),
3071
- api.pageIndex === 0 && props.showTitle !== false && /* @__PURE__ */ jsxs6(
3293
+ api.pageIndex === 0 && props.showTitle !== false && /* @__PURE__ */ jsxs7(
3072
3294
  "header",
3073
3295
  {
3074
3296
  className: slotClass3("fillo-header", appearance, { slot: "header" }),
3075
3297
  "data-fillo": "header",
3076
3298
  children: [
3077
- schema.title && /* @__PURE__ */ jsx7(
3299
+ schema.title && /* @__PURE__ */ jsx8(
3078
3300
  "h1",
3079
3301
  {
3080
3302
  className: slotClass3("fillo-title", appearance, { slot: "title" }),
@@ -3082,7 +3304,7 @@ function ResolvedForm(props) {
3082
3304
  children: schema.title
3083
3305
  }
3084
3306
  ),
3085
- schema.description && /* @__PURE__ */ jsx7(
3307
+ schema.description && /* @__PURE__ */ jsx8(
3086
3308
  "p",
3087
3309
  {
3088
3310
  className: slotClass3("fillo-form-description", appearance, { slot: "description" }),
@@ -3093,7 +3315,7 @@ function ResolvedForm(props) {
3093
3315
  ]
3094
3316
  }
3095
3317
  ),
3096
- multiPage && api.page.title && api.pageIndex > 0 && /* @__PURE__ */ jsx7(
3318
+ multiPage && api.page.title && api.pageIndex > 0 && /* @__PURE__ */ jsx8(
3097
3319
  "h2",
3098
3320
  {
3099
3321
  className: slotClass3("fillo-page-title", appearance, { slot: "pageTitle" }),
@@ -3101,32 +3323,14 @@ function ResolvedForm(props) {
3101
3323
  children: api.page.title
3102
3324
  }
3103
3325
  ),
3104
- invalidFields.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "fillo-error-summary", role: "alert", tabIndex: -1, "data-fillo": "error-summary", children: [
3105
- /* @__PURE__ */ jsx7("p", { className: "fillo-error-summary-title", children: strings.errorSummaryTitle }),
3106
- /* @__PURE__ */ jsx7("ul", { children: invalidFields.map(({ field, error, inputId }) => /* @__PURE__ */ jsx7("li", { children: /* @__PURE__ */ jsxs6(
3107
- "a",
3108
- {
3109
- href: `#${inputId}`,
3110
- onClick: (e) => {
3111
- e.preventDefault();
3112
- focusField(inputId);
3113
- },
3114
- children: [
3115
- field.label,
3116
- ": ",
3117
- error
3118
- ]
3119
- }
3120
- ) }, field.id)) })
3121
- ] }),
3122
- /* @__PURE__ */ jsx7(
3326
+ /* @__PURE__ */ jsx8(
3123
3327
  "div",
3124
3328
  {
3125
3329
  className: slotClass3("fillo-blocks", appearance, { slot: "blocks" }),
3126
3330
  "data-fillo": "blocks",
3127
3331
  ref: blocksRef,
3128
3332
  tabIndex: -1,
3129
- children: api.blocks.map((block) => /* @__PURE__ */ jsx7(
3333
+ children: api.blocks.map((block) => /* @__PURE__ */ jsx8(
3130
3334
  BlockRenderer,
3131
3335
  {
3132
3336
  block,
@@ -3138,7 +3342,7 @@ function ResolvedForm(props) {
3138
3342
  ))
3139
3343
  }
3140
3344
  ),
3141
- !displayOnly && /* @__PURE__ */ jsx7(
3345
+ !displayOnly && /* @__PURE__ */ jsx8(
3142
3346
  "input",
3143
3347
  {
3144
3348
  type: "text",
@@ -3155,8 +3359,8 @@ function ResolvedForm(props) {
3155
3359
  }
3156
3360
  }
3157
3361
  ),
3158
- challenge && showChallenge && /* @__PURE__ */ jsxs6("div", { className: "fillo-turnstile-slot", "data-fillo": "turnstile-slot", children: [
3159
- /* @__PURE__ */ jsx7(
3362
+ challenge && showChallenge && /* @__PURE__ */ jsxs7("div", { className: "fillo-turnstile-slot", "data-fillo": "turnstile-slot", children: [
3363
+ /* @__PURE__ */ jsx8(
3160
3364
  TurnstileWidget,
3161
3365
  {
3162
3366
  siteKey: challenge.siteKey,
@@ -3166,24 +3370,24 @@ function ResolvedForm(props) {
3166
3370
  registerReset: registerChallengeReset
3167
3371
  }
3168
3372
  ),
3169
- challengeError && /* @__PURE__ */ jsx7("p", { className: "fillo-turnstile-error", role: "alert", children: strings.challengeUnavailable })
3373
+ challengeError && /* @__PURE__ */ jsx8("p", { className: "fillo-turnstile-error", role: "alert", children: strings.challengeUnavailable })
3170
3374
  ] }),
3171
- (api.status === "error" || api.submitError) && /* @__PURE__ */ jsxs6("p", { className: "fillo-submit-error", role: "alert", children: [
3375
+ (api.status === "error" || api.submitError) && /* @__PURE__ */ jsxs7("p", { className: "fillo-submit-error", role: "alert", children: [
3172
3376
  api.submitError ?? strings.submitFailed,
3173
- submitFixUrl && /* @__PURE__ */ jsxs6(Fragment3, { children: [
3377
+ submitFixUrl && /* @__PURE__ */ jsxs7(Fragment3, { children: [
3174
3378
  " ",
3175
3379
  "Connect storage:",
3176
3380
  " ",
3177
- /* @__PURE__ */ jsx7("a", { href: submitFixUrl, target: "_blank", rel: "noopener noreferrer", children: submitFixUrl })
3381
+ /* @__PURE__ */ jsx8("a", { href: submitFixUrl, target: "_blank", rel: "noopener noreferrer", children: submitFixUrl })
3178
3382
  ] })
3179
3383
  ] }),
3180
- showFooter && /* @__PURE__ */ jsxs6(
3384
+ showFooter && /* @__PURE__ */ jsxs7(
3181
3385
  "footer",
3182
3386
  {
3183
3387
  className: slotClass3("fillo-footer", appearance, { slot: "footer" }),
3184
3388
  "data-fillo": "footer",
3185
3389
  children: [
3186
- multiPage && !api.isFirstPage && /* @__PURE__ */ jsx7(
3390
+ multiPage && !api.isFirstPage && /* @__PURE__ */ jsx8(
3187
3391
  "button",
3188
3392
  {
3189
3393
  type: "button",
@@ -3196,7 +3400,7 @@ function ResolvedForm(props) {
3196
3400
  children: strings.back
3197
3401
  }
3198
3402
  ),
3199
- showPrimaryButton && /* @__PURE__ */ jsx7(
3403
+ showPrimaryButton && /* @__PURE__ */ jsx8(
3200
3404
  "button",
3201
3405
  {
3202
3406
  type: "submit",
@@ -3215,23 +3419,23 @@ function ResolvedForm(props) {
3215
3419
  ]
3216
3420
  }
3217
3421
  ),
3218
- props.poweredBy && /* @__PURE__ */ jsx7(PoweredBy, { label: props.brandLabel, href: props.brandHref })
3422
+ props.poweredBy && /* @__PURE__ */ jsx8(PoweredBy, { label: props.brandLabel, href: props.brandHref })
3219
3423
  ] });
3220
- return /* @__PURE__ */ jsx7(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx7(FilloChromeContext.Provider, { value: chromeValue, children: /* @__PURE__ */ jsx7(FilloAnnounceContext.Provider, { value: announce, children: /* @__PURE__ */ jsx7(FilloStringsContext.Provider, { value: strings, children: /* @__PURE__ */ jsx7(FilloAppearanceContext.Provider, { value: appearance, children: /* @__PURE__ */ jsx7(FilloInstanceIdContext.Provider, { value: instanceId, children: displayOnly ? (
3424
+ return /* @__PURE__ */ jsx8(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx8(FilloChromeContext.Provider, { value: chromeValue, children: /* @__PURE__ */ jsx8(FilloAnnounceContext.Provider, { value: announce, children: /* @__PURE__ */ jsx8(FilloStringsContext.Provider, { value: strings, children: /* @__PURE__ */ jsx8(FilloAppearanceContext.Provider, { value: appearance, children: /* @__PURE__ */ jsx8(FilloInstanceIdContext.Provider, { value: instanceId, children: displayOnly ? (
3221
3425
  // Display-only preview: a plain div (no form element to submit) with
3222
3426
  // every control inside a natively disabled fieldset — unfocusable
3223
3427
  // and unfillable even without the stylesheet or inert support.
3224
- /* @__PURE__ */ jsx7(
3428
+ /* @__PURE__ */ jsx8(
3225
3429
  "div",
3226
3430
  {
3227
3431
  className: rootClass,
3228
3432
  "data-fillo": "preview-form",
3229
3433
  "data-state": "preview",
3230
3434
  ...themed,
3231
- children: /* @__PURE__ */ jsx7("fieldset", { disabled: true, className: "fillo-not-open-fields", children: formBody })
3435
+ children: /* @__PURE__ */ jsx8("fieldset", { disabled: true, className: "fillo-not-open-fields", children: formBody })
3232
3436
  }
3233
3437
  )
3234
- ) : /* @__PURE__ */ jsx7(
3438
+ ) : /* @__PURE__ */ jsx8(
3235
3439
  "form",
3236
3440
  {
3237
3441
  className: rootClass,
@@ -3246,11 +3450,11 @@ function ResolvedForm(props) {
3246
3450
  e.preventDefault();
3247
3451
  const formEl = e.currentTarget;
3248
3452
  if (api.isLastPage) {
3249
- void api.submit().then(() => focusErrorSummary(formEl)).catch(() => {
3453
+ void api.submit().then(() => focusFirstInvalid(formEl)).catch(() => {
3250
3454
  });
3251
3455
  } else {
3252
3456
  api.next();
3253
- focusErrorSummary(formEl);
3457
+ focusFirstInvalid(formEl);
3254
3458
  }
3255
3459
  },
3256
3460
  children: formBody
@@ -3259,21 +3463,21 @@ function ResolvedForm(props) {
3259
3463
  }
3260
3464
 
3261
3465
  // src/jsx.tsx
3262
- import { useRef as useRef9 } from "react";
3466
+ import { useRef as useRef10 } from "react";
3263
3467
  import {
3264
3468
  codeFormFromJsx,
3265
3469
  FilloJsxError,
3266
3470
  isLikelyDevEnv as isLikelyDevEnv2,
3267
3471
  JSX_BLOCK_COMPONENTS
3268
3472
  } from "@usefillo/core";
3269
- import { jsx as jsx8 } from "react/jsx-runtime";
3473
+ import { jsx as jsx9 } from "react/jsx-runtime";
3270
3474
  var B = JSX_BLOCK_COMPONENTS;
3271
3475
  var isDevEnv = () => isLikelyDevEnv2();
3272
3476
  function JsxForm(props) {
3273
3477
  const { id, title, description, settings, theme, client, children, ...rest } = props;
3274
3478
  const compiled = codeFormFromJsx({ id, title, description, settings, theme }, children);
3275
3479
  const json = JSON.stringify(compiled.schema) + JSON.stringify(compiled.theme ?? null);
3276
- const memo2 = useRef9(null);
3480
+ const memo2 = useRef10(null);
3277
3481
  if (memo2.current && memo2.current.json !== json && isDevEnv()) {
3278
3482
  console.warn(
3279
3483
  `[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.`
@@ -3282,7 +3486,7 @@ function JsxForm(props) {
3282
3486
  if (!memo2.current || memo2.current.json !== json) {
3283
3487
  memo2.current = { json, form: compiled };
3284
3488
  }
3285
- return /* @__PURE__ */ jsx8(FilloForm, { ...{ form: memo2.current.form, client, ...rest } });
3489
+ return /* @__PURE__ */ jsx9(FilloForm, { ...{ form: memo2.current.form, client, ...rest } });
3286
3490
  }
3287
3491
  function defineFormFromJsx(element) {
3288
3492
  if (!element || element.type !== JsxForm) {
@@ -3327,7 +3531,7 @@ var Fillo = {
3327
3531
  };
3328
3532
 
3329
3533
  // src/provider.tsx
3330
- import { useEffect as useEffect9, useId as useId3, useMemo as useMemo6, useState as useState7 } from "react";
3534
+ import { useEffect as useEffect10, useId as useId3, useMemo as useMemo7, useState as useState7 } from "react";
3331
3535
  import {
3332
3536
  FilloError as FilloError3,
3333
3537
  createClient as createClient2,
@@ -3337,7 +3541,7 @@ import {
3337
3541
  resolveStrings as resolveStrings2,
3338
3542
  syncCodeForm as syncCodeForm3
3339
3543
  } from "@usefillo/core";
3340
- import { Fragment as Fragment4, jsx as jsx9 } from "react/jsx-runtime";
3544
+ import { Fragment as Fragment4, jsx as jsx10 } from "react/jsx-runtime";
3341
3545
  function FilloProvider({
3342
3546
  children,
3343
3547
  form,
@@ -3353,7 +3557,7 @@ function FilloProvider({
3353
3557
  ...options
3354
3558
  }) {
3355
3559
  const localPreview = renderOnly === true || skipValidation === true;
3356
- const activeClient = useMemo6(
3560
+ const activeClient = useMemo7(
3357
3561
  () => localPreview ? void 0 : client ?? (formId ? createClient2() : void 0),
3358
3562
  [client, formId, localPreview]
3359
3563
  );
@@ -3363,7 +3567,7 @@ function FilloProvider({
3363
3567
  const devChrome = preview === true || devEnv;
3364
3568
  if (preview === true) warnPreviewInProduction();
3365
3569
  const codeForm = isCodeForm2(form) ? form : null;
3366
- const targetError = useMemo6(
3570
+ const targetError = useMemo7(
3367
3571
  () => !formId && !localPreview && (!codeForm || !client) ? new FilloError3(
3368
3572
  "This form has no Fillo target. Pass formId with the schema, use a defineForm() value with client, or set renderOnly for a non-submitting preview.",
3369
3573
  422,
@@ -3372,7 +3576,7 @@ function FilloProvider({
3372
3576
  ) : null,
3373
3577
  [client, codeForm, formId, localPreview]
3374
3578
  );
3375
- useEffect9(() => {
3579
+ useEffect10(() => {
3376
3580
  if (targetError) onError?.(targetError);
3377
3581
  }, [onError, targetError]);
3378
3582
  const {
@@ -3392,12 +3596,12 @@ function FilloProvider({
3392
3596
  });
3393
3597
  const [resyncWarning, setResyncWarning] = useState7(null);
3394
3598
  const rawSchema = codeForm ? resolvedSchema && !devChrome ? resolvedSchema : codeForm.schema : form;
3395
- const normalized = useMemo6(() => normalizeFormSchema2(rawSchema), [rawSchema]);
3599
+ const normalized = useMemo7(() => normalizeFormSchema2(rawSchema), [rawSchema]);
3396
3600
  if (!normalized.ok) {
3397
3601
  throw new FilloError3(`This form could not be rendered: ${normalized.error}`, 422);
3398
3602
  }
3399
3603
  const schema = normalized.schema;
3400
- const resolveFormId = useMemo6(
3604
+ const resolveFormId = useMemo7(
3401
3605
  () => codeForm && activeClient?.key ? async () => {
3402
3606
  try {
3403
3607
  const result = await syncCodeForm3(activeClient, codeForm, { bypassCache: true });
@@ -3483,7 +3687,7 @@ function FilloProvider({
3483
3687
  const freshWarningUrl = resyncWarning ? resyncWarning.url : warningUrl;
3484
3688
  const freshUploadsAvailable = resyncWarning ? resyncWarning.uploadsAvailable : uploadsAvailable;
3485
3689
  const freshUploadFileSizeLimitMb = resyncWarning ? resyncWarning.uploadFileSizeLimitMb : uploadFileSizeLimitMb;
3486
- const chromeValue = useMemo6(
3690
+ const chromeValue = useMemo7(
3487
3691
  () => ({
3488
3692
  devChrome,
3489
3693
  renderOnly: localPreview,
@@ -3503,11 +3707,11 @@ function FilloProvider({
3503
3707
  );
3504
3708
  const resolvedStrings = resolveStrings2(strings);
3505
3709
  if (targetError) {
3506
- if (renderError) return /* @__PURE__ */ jsx9(Fragment4, { children: renderError(targetError) });
3710
+ if (renderError) return /* @__PURE__ */ jsx10(Fragment4, { children: renderError(targetError) });
3507
3711
  throw targetError;
3508
3712
  }
3509
3713
  const canonicalSyncError = syncError && !localPreview ? syncError : null;
3510
- if (canonicalSyncError && renderError) return /* @__PURE__ */ jsx9(Fragment4, { children: renderError(canonicalSyncError) });
3714
+ if (canonicalSyncError && renderError) return /* @__PURE__ */ jsx10(Fragment4, { children: renderError(canonicalSyncError) });
3511
3715
  if (canonicalSyncError && !devChrome) return null;
3512
3716
  if (codeFormSyncing && !localPreview && !devChrome) return null;
3513
3717
  const notPublished = Boolean(codeForm) && syncedStatus === "draft" && !localPreview;
@@ -3519,9 +3723,9 @@ function FilloProvider({
3519
3723
  void 0,
3520
3724
  syncedAcceptingReason ?? "form_not_accepting"
3521
3725
  );
3522
- return renderError ? /* @__PURE__ */ jsx9(Fragment4, { children: renderError(error) }) : null;
3726
+ return renderError ? /* @__PURE__ */ jsx10(Fragment4, { children: renderError(error) }) : null;
3523
3727
  }
3524
- 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 }) }) }) }) });
3728
+ return /* @__PURE__ */ jsx10(FilloContext.Provider, { value: api, children: /* @__PURE__ */ jsx10(FilloChromeContext.Provider, { value: chromeValue, children: /* @__PURE__ */ jsx10(FilloStringsContext.Provider, { value: resolvedStrings, children: /* @__PURE__ */ jsx10(FilloAppearanceContext.Provider, { value: appearance, children: /* @__PURE__ */ jsx10(FilloInstanceIdContext.Provider, { value: instanceId, children }) }) }) }) });
3525
3729
  }
3526
3730
 
3527
3731
  // src/index.ts