@formancy/react 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -27,8 +27,8 @@ export declare function useFormText(): FormText$1;
27
27
  //#endregion
28
28
  //#region src/use-field.d.ts
29
29
  interface FieldBinding extends FieldSnapshot {
30
- setValue(value: unknown): void;
31
- touch(): void;
30
+ setValue(this: void, value: unknown): void;
31
+ touch(this: void): void;
32
32
  /** Spread onto the input/select/textarea element. */
33
33
  controlProps: ControlProps;
34
34
  /** Spread onto the label element. */
@@ -395,7 +395,7 @@ interface UploadOptions {
395
395
  /** Aborted when the person cancels this file. Hand it to `fetch` and the transfer stops. */
396
396
  signal: AbortSignal;
397
397
  /** Bytes sent so far, of how many. `fetch` cannot report an upload's progress; XHR can. */
398
- onProgress(sent: number, total: number): void;
398
+ onProgress(this: void, sent: number, total: number): void;
399
399
  }
400
400
  /**
401
401
  * Uploads one file and reports what was stored.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/context.tsx","../src/use-field.ts","../src/use-repeater.ts","../src/use-wizard.ts","../src/use-submit.ts","../src/go-to-field.ts","../src/fields/internals.tsx","../src/form.tsx","../src/error-summary.tsx","../src/rich-text.tsx","../src/options-source.ts","../src/scanning.ts","../src/uploads.ts","../src/rich-text-editor.ts","../src/resume-notice.tsx"],"mappings":";;;;;wBAsBgB,mBACd,QACA,OACA;EAEA,QAAQ;;;;;;;EAOR,QAAQ;EACR,WAAW;oBACZ,IAAA;wBAsBe,iBAAiB;;;;;;;;;wBAkBjB,eAAe;;;UCvEd,qBAAqB;EACpC,SAAS;EACT;;EAEA,cAAc;;EAEd;IAAc;IAAY;;;EAE1B;IAAc;;;;;;;;;;;;;wBAaA,SAAS,eAAe,OAAO;;;UCrB9B;EACf;;EAEA;;EAEA;;EAEA;EACA;EACA,UAAU;;EAEV,QAAQ,cAAc;;;;;;;wBAQR,YAAY,eAAe,OAAO;;;UCrBjC;EACf;EACA;;;EAGA;EACA;;EAEA,QAAQ;EACR;;EAEA,KAAK;;;;;;;wBAQS,aAAa;;;;;;;;;;;wBCTb;EAAqB;EAAa,QAAQ;;;;;;;;;;;wBCD1C,iBAAiB;;;;;;;;;;;;;;;;UCYhB;EACf;EACA;;KAGU,iBAAiB,cAAc;UAE1B;EACf,SAAS,QAAQ,OAAO,WAAW;EACnC,SAAS,eAAe;;UAGT;EACf;EACA,QAAQ;;EAER;;UAGe;;;;;;;EAOf,SAAS;EACT,WAAW;;;;;;;EAOX;EACA;;;;;EAKA;EACA,YAAY,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;wBCNP,aAAa,OAAO,oCAAiB,IAAA;;;UCrDpC;EACf,SAAS;;;;;;;;;;;wBAYK,eAAe,UAAU,oCAAiB,IAAA;;;;;;;;;;;;;;;;;;;;wBCG1C,WAAW;EAAY;IAAmB;;;;;;;;;;UCZzC;;;;;EAKf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;;EASA;;;;;;;;EAQA;;EAEA,QAAQ;;;;;;;;;;;UAYO;;;;;;;;;;;;;EAaf,QAAQ,SAAS,iBAAiB,QAAQ;;EAE1C;;;;;;EAMA;;EAEA;;;;;;;;;;;;;KAcU,iBAAiB,SAAS,eAAe;qBAIxC,wCAAsB,SAAA,SAAA,eAAA;;;;;;;;;wBAUnB,qBAAqB;;;;;;;;;;;;UC9FpB;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;KAoBU,WAAW,SAAS,gBAAgB;qBAInC,iCAAe,SAAA;;;;;;;;wBASZ,cAAc;;;;;;;;;;;UC/Bb;;;;;EAKf;;EAEA,QAAQ;;EAER,WAAW,cAAc;;;;;;;;;;;;KAaf,YAAY,MAAM,MAAM,SAAS,kBAAkB,QAAQ;qBAI1D,kCAAgB,SAAA;;;;;;;;;wBAUb,eAAe;;;;;;;;;;;;;;;;;;;;;UChCd;;EAEf;;EAEA,WAAW;;;;;;;;;;;;;EAaX,MAAM,SAAS,aAAa;;EAE5B,WAAW,SAAS;EACpB;;UAGe;;WAEN,SAAS;;WAET;;WAEA,WAAW;WACX;;;;;;;;;;WAUA,YAAY,SAAS;;KAGpB,yBAAyB,OAAO,wBAAwB;qBAIvD,wCAAsB,SAAA;;;;;;;;;wBAUnB,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UC9C3B;WACN;WACA,SAAS;aAAyB;aAAuB;;;UAGnD;;WAEN,YAAY;;WAEZ,SAAS,SAAS;;wBAGb,eAAe,WAAW,UAAU,oBAAoB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/context.tsx","../src/use-field.ts","../src/use-repeater.ts","../src/use-wizard.ts","../src/use-submit.ts","../src/go-to-field.ts","../src/fields/internals.tsx","../src/form.tsx","../src/error-summary.tsx","../src/rich-text.tsx","../src/options-source.ts","../src/scanning.ts","../src/uploads.ts","../src/rich-text-editor.ts","../src/resume-notice.tsx"],"mappings":";;;;;wBAsBgB,mBACd,QACA,OACA;EAEA,QAAQ;;;;;;;EAOR,QAAQ;EACR,WAAW;oBACZ,IAAA;wBAsBe,iBAAiB;;;;;;;;;wBAkBjB,eAAe;;;UCvEd,qBAAqB;EACpC,SAAS,YAAY;EACrB,MAAM;;EAEN,cAAc;;EAEd;IAAc;IAAY;;;EAE1B;IAAc;;;;;;;;;;;;;wBAaA,SAAS,eAAe,OAAO;;;UCrB9B;EACf;;EAEA;;EAEA;;EAEA;EACA;EACA,UAAU;;EAEV,QAAQ,cAAc;;;;;;;wBAQR,YAAY,eAAe,OAAO;;;UCrBjC;EACf;EACA;;;EAGA;EACA;;EAEA,QAAQ;EACR;;EAEA,KAAK;;;;;;;wBAQS,aAAa;;;;;;;;;;;wBCTb;EAAqB;EAAa,QAAQ;;;;;;;;;;;wBCD1C,iBAAiB;;;;;;;;;;;;;;;;UCWhB;EACf;EACA;;KAGU,iBAAiB,cAAc;UAE1B;EACf,SAAS,QAAQ,OAAO,WAAW;EACnC,SAAS,eAAe;;UAGT;EACf;EACA,QAAQ;;EAER;;UAGe;;;;;;;EAOf,SAAS;EACT,WAAW;;;;;;;EAOX;EACA;;;;;EAKA;EACA,YAAY,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;wBCxBP,aAAa,OAAO,oCAAiB,IAAA;;;UClCpC;EACf,SAAS;;;;;;;;;;;wBAYK,eAAe,UAAU,oCAAiB,IAAA;;;;;;;;;;;;;;;;;;;;wBCG1C,WAAW;EAAY;IAAmB;;;;;;;;;;UCZzC;;;;;EAKf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;;EASA;;;;;;;;EAQA;;EAEA,QAAQ;;;;;;;;;;;UAYO;;;;;;;;;;;;;EAaf,QAAQ,SAAS,iBAAiB,QAAQ;;EAE1C;;;;;;EAMA;;EAEA;;;;;;;;;;;;;KAcU,iBAAiB,SAAS,eAAe;qBAIxC,wCAAsB,SAAA,SAAA,eAAA;;;;;;;;;wBAUnB,qBAAqB;;;;;;;;;;;;UC9FpB;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;KAoBU,WAAW,SAAS,gBAAgB;qBAInC,iCAAe,SAAA;;;;;;;;wBASZ,cAAc;;;;;;;;;;;UC/Bb;;;;;EAKf;;EAEA,QAAQ;;EAER,WAAW,YAAY,cAAc;;;;;;;;;;;;KAa3B,YAAY,MAAM,MAAM,SAAS,kBAAkB,QAAQ;qBAI1D,kCAAgB,SAAA;;;;;;;;;wBAUb,eAAe;;;;;;;;;;;;;;;;;;;;;UChCd;;EAEf;;EAEA,WAAW;;;;;;;;;;;;;EAaX,MAAM,SAAS,aAAa;;EAE5B,WAAW,SAAS;EACpB;;UAGe;;WAEN,SAAS;;WAET;;WAEA,WAAW;WACX;;;;;;;;;;WAUA,YAAY,SAAS;;KAGpB,yBAAyB,OAAO,wBAAwB;qBAIvD,wCAAsB,SAAA;;;;;;;;;wBAUnB,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UC9C3B;WACN;WACA,SAAS;aAAyB;aAAuB;;;UAGnD;;WAEN,YAAY;;WAEZ,SAAS,SAAS;;wBAGb,eAAe,WAAW,UAAU,oBAAoB"}
package/dist/index.mjs CHANGED
@@ -122,7 +122,7 @@ function focusControl(control) {
122
122
  if (control === null) return;
123
123
  control.dispatchEvent(new Event("formancy-reveal", { bubbles: true }));
124
124
  control.focus();
125
- if (document.activeElement !== control) control.querySelector("input, select, textarea, button")?.focus();
125
+ if (document.activeElement !== control) control.querySelector("input, select, textarea, button, [data-formancy-part$=\"-unavailable\"]")?.focus();
126
126
  }
127
127
  //#endregion
128
128
  //#region src/use-submit.ts
@@ -337,7 +337,7 @@ function CodeNode({ schema, node, locale }) {
337
337
  const label = resolveText(schema, node.label, locale);
338
338
  const labelId = useId();
339
339
  const value = useField(node.path).value;
340
- const text = typeof value === "string" ? value : value === null || value === void 0 ? "" : String(value);
340
+ const text = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" ? String(value) : "";
341
341
  return /* @__PURE__ */ jsxs("div", {
342
342
  "data-formancy-part": "code",
343
343
  "data-state": text === "" ? "empty" : "ready",
@@ -378,10 +378,15 @@ function CodeDrawing({ value }) {
378
378
  children: modules
379
379
  });
380
380
  }
381
- function placedPaths(nodes, into = []) {
382
- for (const node of nodes) if (node.kind === "field") into.push(node.path);
383
- else placedPaths(layoutChildren(node), into);
384
- return into;
381
+ //#endregion
382
+ //#region src/focus-row.ts
383
+ function focusRow(repeater, target, add) {
384
+ if (target === "add") return focusControl(add);
385
+ const row = repeater.querySelectorAll("[data-formancy-part=\"row\"], [data-formancy-part=\"datagrid-row\"]")[target.row];
386
+ if (row === void 0) return;
387
+ const part = target.parts.map((name) => row.querySelector(`[data-formancy-part="${name}"]`)).find((found) => found !== null);
388
+ const first = row.querySelector("input:not([type=\"hidden\"]):not(:disabled), select:not(:disabled), textarea:not(:disabled), button:not(:disabled), [contenteditable=\"true\"], [tabindex=\"0\"]");
389
+ focusControl(part ?? first);
385
390
  }
386
391
  //#endregion
387
392
  //#region src/review.tsx
@@ -417,12 +422,12 @@ function useReview(goTo) {
417
422
  const goToField = useGoToField();
418
423
  return {
419
424
  reviewing,
420
- check() {
425
+ check: () => {
421
426
  if (submit().ok) return setReviewing(true);
422
427
  const firstInvalid = engine.firstInvalid();
423
428
  if (firstInvalid !== null) goTo?.(engine.pageOf(parsePath(firstInvalid)));
424
429
  },
425
- change(answer) {
430
+ change: (answer) => {
426
431
  setReviewing(false);
427
432
  goToField(answer.field);
428
433
  }
@@ -488,48 +493,103 @@ function Review({ onChange, children }) {
488
493
  });
489
494
  }
490
495
  //#endregion
491
- //#region src/rich-text.tsx
492
- function RichText({ source }) {
493
- const blocks = parseRichText(source);
494
- if (blocks.length === 0) return null;
495
- return /* @__PURE__ */ jsx("div", {
496
- "data-formancy-part": "richtext",
497
- children: blocks.map((block, index) => /* @__PURE__ */ jsx(Block, { block }, index))
496
+ //#region src/fields/internals.tsx
497
+ function ErrorText({ field }) {
498
+ const text = useFormText();
499
+ return /* @__PURE__ */ jsx("p", {
500
+ "data-formancy-part": "error",
501
+ ...field.errorProps,
502
+ children: field.errors.map((code, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 ? " " : null, /* @__PURE__ */ jsx("span", {
503
+ "data-formancy-code": code,
504
+ children: errorSentence(text, field.def, code, field.messages)
505
+ })] }, code))
498
506
  });
499
507
  }
500
- function Block({ block }) {
501
- if (block.kind === "paragraph") return /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(Inlines, { nodes: block.children }) });
502
- const items = block.items.map((item, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Inlines, { nodes: item }) }, index));
503
- return block.ordered ? /* @__PURE__ */ jsx("ol", { children: items }) : /* @__PURE__ */ jsx("ul", { children: items });
508
+ function FieldShell({ path, field, label, children }) {
509
+ return /* @__PURE__ */ jsxs("div", {
510
+ "data-formancy-part": "field",
511
+ "data-formancy-field-path": path,
512
+ "data-state": field.touched && field.errors.length > 0 ? "invalid" : "valid",
513
+ children: [
514
+ /* @__PURE__ */ jsx("label", {
515
+ "data-formancy-part": "label",
516
+ ...field.labelProps,
517
+ children: label
518
+ }),
519
+ /* @__PURE__ */ jsx(Description, { field }),
520
+ children,
521
+ field.touched && field.errors.length > 0 ? /* @__PURE__ */ jsx(ErrorText, { field }) : null
522
+ ]
523
+ });
504
524
  }
505
- function Inlines({ nodes }) {
506
- return /* @__PURE__ */ jsx(Fragment$1, { children: nodes.map((node, index) => /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Inline, { node }) }, index)) });
525
+ function statusState({ sourced, said, open, matches }) {
526
+ if (sourced.remote === null) return open && matches === 0 ? "empty" : void 0;
527
+ if (sourced.remote.busy) return "busy";
528
+ if (sourced.remote.failed) return "failed";
529
+ if (said !== "") return "hint";
530
+ return open && matches === 0 ? "empty" : void 0;
507
531
  }
508
- function Inline({ node }) {
509
- switch (node.kind) {
510
- case "text": return /* @__PURE__ */ jsx(Fragment$1, { children: node.text });
511
- case "strong": return /* @__PURE__ */ jsx("strong", { children: /* @__PURE__ */ jsx(Inlines, { nodes: node.children }) });
512
- case "emphasis": return /* @__PURE__ */ jsx("em", { children: /* @__PURE__ */ jsx(Inlines, { nodes: node.children }) });
513
- case "link": return /* @__PURE__ */ jsx("a", {
514
- href: node.href,
515
- rel: "noreferrer noopener nofollow ugc",
516
- children: /* @__PURE__ */ jsx(Inlines, { nodes: node.children })
517
- });
518
- }
532
+ function RequiredHint({ field }) {
533
+ const text = useFormText();
534
+ if (!field.required) return null;
535
+ return /* @__PURE__ */ jsx("span", {
536
+ "data-formancy-part": "required-hint",
537
+ id: field.props.hint.id,
538
+ children: text("form.required")
539
+ });
519
540
  }
520
- //#endregion
521
- //#region src/rich-text-editor.ts
522
- const RichTextEditorContext = createContext(void 0);
523
- const RichTextEditorProvider = RichTextEditorContext.Provider;
524
- function useRichTextEditorFactory() {
525
- return useContext(RichTextEditorContext);
541
+ function Description({ field }) {
542
+ if (field.description === void 0) return null;
543
+ return /* @__PURE__ */ jsx("p", {
544
+ "data-formancy-part": "description",
545
+ id: field.props.description.id,
546
+ children: field.description
547
+ });
548
+ }
549
+ function usePlaceholder(field) {
550
+ return useFormEngine().text(field.def.placeholder);
551
+ }
552
+ function optionDomId(field, value) {
553
+ return `${field.ids.control}:option:${value}`;
554
+ }
555
+ function useResolvedOptions(field) {
556
+ const engine = useFormEngine();
557
+ return inOptionOrder((field.def.options ?? []).map((option) => ({
558
+ value: option.value,
559
+ label: engine.text(option.label) ?? option.value,
560
+ ...option.image !== void 0 && isImageSource(option.image.src) ? { image: {
561
+ src: option.image.src,
562
+ alt: engine.text(option.image.alt) ?? ""
563
+ } } : {}
564
+ })), field.optionOrder);
565
+ }
566
+ function OptionPicture({ option }) {
567
+ if (option.image === void 0) return null;
568
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("img", {
569
+ "data-formancy-part": "option-image",
570
+ src: option.image.src,
571
+ alt: option.image.alt,
572
+ loading: "lazy",
573
+ decoding: "async"
574
+ }), " "] });
526
575
  }
527
576
  //#endregion
528
- //#region src/scanning.ts
529
- const ScannerContext = createContext(void 0);
530
- const ScannerProvider = ScannerContext.Provider;
531
- function useScanner() {
532
- return useContext(ScannerContext);
577
+ //#region src/fields/computed-answer.tsx
578
+ function ComputedAnswerField({ path, label }) {
579
+ const field = useField(path);
580
+ const engine = useFormEngine();
581
+ const text = useFormText();
582
+ return /* @__PURE__ */ jsx(FieldShell, {
583
+ path,
584
+ field,
585
+ label,
586
+ children: /* @__PURE__ */ jsx("input", {
587
+ type: "text",
588
+ readOnly: true,
589
+ ...field.controlProps,
590
+ value: answerInWords(engine.schema(), field.def, field.value, text, engine.locale())
591
+ })
592
+ });
533
593
  }
534
594
  //#endregion
535
595
  //#region src/options-source.ts
@@ -693,94 +753,6 @@ function storedValues(value) {
693
753
  return value.filter((item) => typeof item === "string" && item !== "");
694
754
  }
695
755
  //#endregion
696
- //#region src/uploads.ts
697
- const UploaderContext = createContext(void 0);
698
- const UploaderProvider = UploaderContext.Provider;
699
- function useUploader() {
700
- return useContext(UploaderContext);
701
- }
702
- //#endregion
703
- //#region src/fields/internals.tsx
704
- function ErrorText({ field }) {
705
- const text = useFormText();
706
- return /* @__PURE__ */ jsx("p", {
707
- "data-formancy-part": "error",
708
- ...field.errorProps,
709
- children: field.errors.map((code, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 ? " " : null, /* @__PURE__ */ jsx("span", {
710
- "data-formancy-code": code,
711
- children: errorSentence(text, field.def, code, field.messages)
712
- })] }, code))
713
- });
714
- }
715
- function FieldShell({ path, field, label, children }) {
716
- return /* @__PURE__ */ jsxs("div", {
717
- "data-formancy-part": "field",
718
- "data-formancy-field-path": path,
719
- "data-state": field.touched && field.errors.length > 0 ? "invalid" : "valid",
720
- children: [
721
- /* @__PURE__ */ jsx("label", {
722
- "data-formancy-part": "label",
723
- ...field.labelProps,
724
- children: label
725
- }),
726
- /* @__PURE__ */ jsx(Description, { field }),
727
- children,
728
- field.touched && field.errors.length > 0 ? /* @__PURE__ */ jsx(ErrorText, { field }) : null
729
- ]
730
- });
731
- }
732
- function statusState({ sourced, said, open, matches }) {
733
- if (sourced.remote === null) return open && matches === 0 ? "empty" : void 0;
734
- if (sourced.remote.busy) return "busy";
735
- if (sourced.remote.failed) return "failed";
736
- if (said !== "") return "hint";
737
- return open && matches === 0 ? "empty" : void 0;
738
- }
739
- function RequiredHint({ field }) {
740
- const text = useFormText();
741
- if (!field.required) return null;
742
- return /* @__PURE__ */ jsx("span", {
743
- "data-formancy-part": "required-hint",
744
- id: field.props.hint.id,
745
- children: text("form.required")
746
- });
747
- }
748
- function Description({ field }) {
749
- if (field.description === void 0) return null;
750
- return /* @__PURE__ */ jsx("p", {
751
- "data-formancy-part": "description",
752
- id: field.props.description.id,
753
- children: field.description
754
- });
755
- }
756
- function usePlaceholder(field) {
757
- return useFormEngine().text(field.def.placeholder);
758
- }
759
- function optionDomId(field, value) {
760
- return `${field.ids.control}:option:${value}`;
761
- }
762
- function useResolvedOptions(field) {
763
- const engine = useFormEngine();
764
- return inOptionOrder((field.def.options ?? []).map((option) => ({
765
- value: option.value,
766
- label: engine.text(option.label) ?? option.value,
767
- ...option.image !== void 0 && isImageSource(option.image.src) ? { image: {
768
- src: option.image.src,
769
- alt: engine.text(option.image.alt) ?? ""
770
- } } : {}
771
- })), field.optionOrder);
772
- }
773
- function OptionPicture({ option }) {
774
- if (option.image === void 0) return null;
775
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("img", {
776
- "data-formancy-part": "option-image",
777
- src: option.image.src,
778
- alt: option.image.alt,
779
- loading: "lazy",
780
- decoding: "async"
781
- }), " "] });
782
- }
783
- //#endregion
784
756
  //#region src/fields/typeahead-field.tsx
785
757
  function TypeaheadSelectField({ path, label, field }) {
786
758
  const text = useFormText();
@@ -809,6 +781,10 @@ function TypeaheadSelectField({ path, label, field }) {
809
781
  const activeIndex = matches.findIndex((option) => option.value === activeValue);
810
782
  const activeId = expanded && activeIndex !== -1 ? optionDomId(field, matches[activeIndex].value) : void 0;
811
783
  const listboxId = `${field.ids.control}:listbox`;
784
+ const listbox = useRef(null);
785
+ useEffect(() => {
786
+ if (expanded && activeIndex !== -1) listbox.current?.children[activeIndex]?.scrollIntoView?.({ block: "nearest" });
787
+ }, [expanded, activeIndex]);
812
788
  const choose = (value) => {
813
789
  field.setValue(value);
814
790
  setQuery(null);
@@ -892,6 +868,7 @@ function TypeaheadSelectField({ path, label, field }) {
892
868
  onKeyDown,
893
869
  onBlur
894
870
  }), /* @__PURE__ */ jsx("ul", {
871
+ ref: listbox,
895
872
  id: listboxId,
896
873
  role: "listbox",
897
874
  "aria-label": text("options.suggestions", { label }),
@@ -969,6 +946,8 @@ function SelectField({ path, label }) {
969
946
  label,
970
947
  children: /* @__PURE__ */ jsx("p", {
971
948
  "data-formancy-part": "options-unavailable",
949
+ id: field.controlProps.id,
950
+ tabIndex: -1,
972
951
  children: text("options.unavailable", { source: field.def.optionsSource ?? "" })
973
952
  })
974
953
  });
@@ -1042,22 +1021,11 @@ function RadioGroupField({ path, label }) {
1042
1021
  });
1043
1022
  }
1044
1023
  //#endregion
1045
- //#region src/fields/computed-answer.tsx
1046
- function ComputedAnswerField({ path, label }) {
1047
- const field = useField(path);
1048
- const engine = useFormEngine();
1049
- const text = useFormText();
1050
- return /* @__PURE__ */ jsx(FieldShell, {
1051
- path,
1052
- field,
1053
- label,
1054
- children: /* @__PURE__ */ jsx("input", {
1055
- type: "text",
1056
- readOnly: true,
1057
- ...field.controlProps,
1058
- value: answerInWords(engine.schema(), field.def, field.value, text, engine.locale())
1059
- })
1060
- });
1024
+ //#region src/uploads.ts
1025
+ const UploaderContext = createContext(void 0);
1026
+ const UploaderProvider = UploaderContext.Provider;
1027
+ function useUploader() {
1028
+ return useContext(UploaderContext);
1061
1029
  }
1062
1030
  //#endregion
1063
1031
  //#region src/fields/file-field.tsx
@@ -1083,6 +1051,8 @@ function FileField({ path, label }) {
1083
1051
  children: [
1084
1052
  upload === void 0 ? /* @__PURE__ */ jsx("p", {
1085
1053
  "data-formancy-part": "file-unavailable",
1054
+ id: field.controlProps.id,
1055
+ tabIndex: -1,
1086
1056
  children: text("file.unavailable")
1087
1057
  }) : /* @__PURE__ */ jsx("div", {
1088
1058
  "data-formancy-part": "file-dropzone",
@@ -1301,6 +1271,43 @@ function FileThumbnail({ source }) {
1301
1271
  });
1302
1272
  }
1303
1273
  //#endregion
1274
+ //#region src/rich-text.tsx
1275
+ function RichText({ source }) {
1276
+ const blocks = parseRichText(source);
1277
+ if (blocks.length === 0) return null;
1278
+ return /* @__PURE__ */ jsx("div", {
1279
+ "data-formancy-part": "richtext",
1280
+ children: blocks.map((block, index) => /* @__PURE__ */ jsx(Block, { block }, index))
1281
+ });
1282
+ }
1283
+ function Block({ block }) {
1284
+ if (block.kind === "paragraph") return /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(Inlines, { nodes: block.children }) });
1285
+ const items = block.items.map((item, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Inlines, { nodes: item }) }, index));
1286
+ return block.ordered ? /* @__PURE__ */ jsx("ol", { children: items }) : /* @__PURE__ */ jsx("ul", { children: items });
1287
+ }
1288
+ function Inlines({ nodes }) {
1289
+ return /* @__PURE__ */ jsx(Fragment$1, { children: nodes.map((node, index) => /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Inline, { node }) }, index)) });
1290
+ }
1291
+ function Inline({ node }) {
1292
+ switch (node.kind) {
1293
+ case "text": return /* @__PURE__ */ jsx(Fragment$1, { children: node.text });
1294
+ case "strong": return /* @__PURE__ */ jsx("strong", { children: /* @__PURE__ */ jsx(Inlines, { nodes: node.children }) });
1295
+ case "emphasis": return /* @__PURE__ */ jsx("em", { children: /* @__PURE__ */ jsx(Inlines, { nodes: node.children }) });
1296
+ case "link": return /* @__PURE__ */ jsx("a", {
1297
+ href: node.href,
1298
+ rel: "noreferrer noopener nofollow ugc",
1299
+ children: /* @__PURE__ */ jsx(Inlines, { nodes: node.children })
1300
+ });
1301
+ }
1302
+ }
1303
+ //#endregion
1304
+ //#region src/rich-text-editor.ts
1305
+ const RichTextEditorContext = createContext(void 0);
1306
+ const RichTextEditorProvider = RichTextEditorContext.Provider;
1307
+ function useRichTextEditorFactory() {
1308
+ return useContext(RichTextEditorContext);
1309
+ }
1310
+ //#endregion
1304
1311
  //#region src/fields/rich-text-field.tsx
1305
1312
  function RichTextToolbar({ disabled, label, onCommand }) {
1306
1313
  const buttons = useRef([]);
@@ -1539,6 +1546,8 @@ function TagPickerField({ path, label }) {
1539
1546
  label,
1540
1547
  children: /* @__PURE__ */ jsx("p", {
1541
1548
  "data-formancy-part": "options-unavailable",
1549
+ id: field.controlProps.id,
1550
+ tabIndex: -1,
1542
1551
  children: text("options.unavailable", { source: field.def.optionsSource ?? "" })
1543
1552
  })
1544
1553
  });
@@ -1675,6 +1684,7 @@ function SelectBoxesField({ path, label }) {
1675
1684
  /* @__PURE__ */ jsx(Description, { field }),
1676
1685
  remote?.unavailable === true ? /* @__PURE__ */ jsx("p", {
1677
1686
  "data-formancy-part": "options-unavailable",
1687
+ tabIndex: -1,
1678
1688
  children: text("options.unavailable", { source: field.def.optionsSource ?? "" })
1679
1689
  }) : null,
1680
1690
  options.map((option) => {
@@ -2210,6 +2220,13 @@ function CharacterCount({ field }) {
2210
2220
  })] });
2211
2221
  }
2212
2222
  //#endregion
2223
+ //#region src/scanning.ts
2224
+ const ScannerContext = createContext(void 0);
2225
+ const ScannerProvider = ScannerContext.Provider;
2226
+ function useScanner() {
2227
+ return useContext(ScannerContext);
2228
+ }
2229
+ //#endregion
2213
2230
  //#region src/fields/text-fields.tsx
2214
2231
  function TextField({ path, label }) {
2215
2232
  const field = useField(path);
@@ -2318,6 +2335,30 @@ function TextareaField({ path, label }) {
2318
2335
  });
2319
2336
  }
2320
2337
  //#endregion
2338
+ //#region src/fields.ts
2339
+ const DEFAULT_COMPONENTS = {
2340
+ text: TextField,
2341
+ textarea: TextareaField,
2342
+ number: NumberSwitch,
2343
+ checkbox: CheckboxField,
2344
+ date: DateField,
2345
+ time: TimeField,
2346
+ datetime: DateTimeField,
2347
+ select: SelectField,
2348
+ radio: RadioGroupField,
2349
+ selectboxes: SelectBoxesSwitch,
2350
+ ranking: RankingField,
2351
+ matrix: MatrixField,
2352
+ file: FileField,
2353
+ richtext: RichTextField,
2354
+ signature: SignatureField,
2355
+ hidden: null,
2356
+ static: StaticField,
2357
+ group: null,
2358
+ page: null,
2359
+ repeater: null
2360
+ };
2361
+ //#endregion
2321
2362
  //#region src/form.tsx
2322
2363
  function FormancyForm(props) {
2323
2364
  return useFormEngine().wizard() === void 0 ? /* @__PURE__ */ jsx(FlatForm, { ...props }) : /* @__PURE__ */ jsx(PagedForm, { ...props });
@@ -2390,34 +2431,31 @@ function FieldList({ labels, registry, page, layout }) {
2390
2431
  const repeaterWires = engine.repeaterPaths();
2391
2432
  const schema = engine.schema();
2392
2433
  const arrangement = schema.layouts?.find((candidate) => candidate.name === layout);
2393
- if (layout !== void 0 && arrangement !== void 0) {
2394
- new Set(placedPaths(arrangement.nodes));
2395
- return /* @__PURE__ */ jsx(LayoutTree, {
2396
- schema,
2397
- nodes: arrangement.nodes,
2398
- locale: engine.locale(),
2399
- shows: page === void 0 ? void 0 : (path) => placedPage(engine, path) === page,
2400
- renderField: (path) => {
2401
- if (repeaterWires.includes(path)) return /* @__PURE__ */ jsx(RepeaterSection, {
2402
- wire: path,
2403
- labels,
2404
- registry
2405
- }, path);
2406
- const group = placedGroup(engine, path);
2407
- if (group !== void 0) return /* @__PURE__ */ jsx(GroupSection, {
2408
- path,
2409
- group,
2410
- labels,
2411
- registry
2412
- }, path);
2413
- return /* @__PURE__ */ jsx(FieldSlot, {
2414
- path,
2415
- fallbackLabel: labels?.[path],
2416
- registry
2417
- }, path);
2418
- }
2419
- });
2420
- }
2434
+ if (layout !== void 0 && arrangement !== void 0) return /* @__PURE__ */ jsx(LayoutTree, {
2435
+ schema,
2436
+ nodes: arrangement.nodes,
2437
+ locale: engine.locale(),
2438
+ shows: page === void 0 ? void 0 : (path) => placedPage(engine, path) === page,
2439
+ renderField: (path) => {
2440
+ if (repeaterWires.includes(path)) return /* @__PURE__ */ jsx(RepeaterSection, {
2441
+ wire: path,
2442
+ labels,
2443
+ registry
2444
+ }, path);
2445
+ const group = placedGroup(engine, path);
2446
+ if (group !== void 0) return /* @__PURE__ */ jsx(GroupSection, {
2447
+ path,
2448
+ group,
2449
+ labels,
2450
+ registry
2451
+ }, path);
2452
+ return /* @__PURE__ */ jsx(FieldSlot, {
2453
+ path,
2454
+ fallbackLabel: labels?.[path],
2455
+ registry
2456
+ }, path);
2457
+ }
2458
+ });
2421
2459
  const inPage = (wire) => page === void 0 || engine.pageOf(parsePath(wire)) === page;
2422
2460
  const staticWires = engine.fieldPaths().filter((wire) => !repeaterWires.some((repeater) => wire.startsWith(`${repeater}[`)));
2423
2461
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [staticWires.filter(inPage).map((wire) => /* @__PURE__ */ jsx(FieldSlot, {
@@ -2488,6 +2526,14 @@ function RepeaterSection({ wire, labels, registry }) {
2488
2526
  const label = engine.text(def?.label) ?? labels?.[wire] ?? wire;
2489
2527
  const addLabel = def?.addLabel ?? text("repeater.add", { label });
2490
2528
  const removeLabel = def?.removeLabel ?? text("repeater.remove", { label });
2529
+ const fieldset = useRef(null);
2530
+ const addButton = useRef(null);
2531
+ const [focusing, setFocusing] = useState(void 0);
2532
+ useEffect(() => {
2533
+ if (focusing === void 0 || fieldset.current === null) return;
2534
+ focusRow(fieldset.current, focusing, addButton.current);
2535
+ setFocusing(void 0);
2536
+ }, [focusing]);
2491
2537
  const fallbackFor = (instanceWire) => {
2492
2538
  const template = instanceWire.replace(/\[\d+\]/, "[]");
2493
2539
  return labels?.[template] ?? labels?.[instanceWire];
@@ -2506,7 +2552,13 @@ function RepeaterSection({ wire, labels, registry }) {
2506
2552
  type: "button",
2507
2553
  "data-formancy-part": "row-remove",
2508
2554
  disabled: field.disabled,
2509
- onClick: () => repeater.removeRow(index),
2555
+ onClick: () => {
2556
+ repeater.removeRow(index);
2557
+ setFocusing(repeater.rowCount === 1 ? "add" : {
2558
+ row: Math.min(index, repeater.rowCount - 2),
2559
+ parts: []
2560
+ });
2561
+ },
2510
2562
  children: /* @__PURE__ */ jsx("span", {
2511
2563
  "data-formancy-part": "row-action-text",
2512
2564
  children: text("repeater.removeRow", {
@@ -2516,11 +2568,17 @@ function RepeaterSection({ wire, labels, registry }) {
2516
2568
  })
2517
2569
  })
2518
2570
  }) : null,
2519
- index > 0 ? /* @__PURE__ */ jsx("button", {
2571
+ index > 0 && !field.readOnly ? /* @__PURE__ */ jsx("button", {
2520
2572
  type: "button",
2521
2573
  "data-formancy-part": "row-up",
2522
2574
  disabled: field.disabled,
2523
- onClick: () => repeater.moveRow(index, index - 1),
2575
+ onClick: () => {
2576
+ repeater.moveRow(index, index - 1);
2577
+ setFocusing({
2578
+ row: index - 1,
2579
+ parts: ["row-up", "row-down"]
2580
+ });
2581
+ },
2524
2582
  children: /* @__PURE__ */ jsx("span", {
2525
2583
  "data-formancy-part": "row-action-text",
2526
2584
  children: text("repeater.moveUp", {
@@ -2530,11 +2588,17 @@ function RepeaterSection({ wire, labels, registry }) {
2530
2588
  })
2531
2589
  })
2532
2590
  }) : null,
2533
- index < repeater.rowCount - 1 ? /* @__PURE__ */ jsx("button", {
2591
+ index < repeater.rowCount - 1 && !field.readOnly ? /* @__PURE__ */ jsx("button", {
2534
2592
  type: "button",
2535
2593
  "data-formancy-part": "row-down",
2536
2594
  disabled: field.disabled,
2537
- onClick: () => repeater.moveRow(index, index + 1),
2595
+ onClick: () => {
2596
+ repeater.moveRow(index, index + 1);
2597
+ setFocusing({
2598
+ row: index + 1,
2599
+ parts: ["row-down", "row-up"]
2600
+ });
2601
+ },
2538
2602
  children: /* @__PURE__ */ jsx("span", {
2539
2603
  "data-formancy-part": "row-action-text",
2540
2604
  children: text("repeater.moveDown", {
@@ -2567,6 +2631,7 @@ function RepeaterSection({ wire, labels, registry }) {
2567
2631
  if (!field.visible) return null;
2568
2632
  const showError = field.touched && field.errors.length > 0;
2569
2633
  return /* @__PURE__ */ jsxs("fieldset", {
2634
+ ref: fieldset,
2570
2635
  "data-formancy-part": "repeater",
2571
2636
  "data-formancy-field-path": wire,
2572
2637
  "data-state": showError ? "invalid" : "valid",
@@ -2593,37 +2658,27 @@ function RepeaterSection({ wire, labels, registry }) {
2593
2658
  }) : null, rows]
2594
2659
  }) : rows,
2595
2660
  showError ? /* @__PURE__ */ jsx(ErrorText, { field }) : null,
2661
+ def?.maxItems === void 0 || field.readOnly ? null : /* @__PURE__ */ jsx("p", {
2662
+ role: "status",
2663
+ "data-formancy-part": "repeater-status",
2664
+ children: repeater.canAdd ? "" : text("repeater.full", { max: def.maxItems })
2665
+ }),
2596
2666
  repeater.canAdd ? /* @__PURE__ */ jsx("button", {
2597
2667
  type: "button",
2668
+ ref: addButton,
2598
2669
  disabled: field.disabled,
2599
- onClick: () => repeater.addRow(),
2670
+ onClick: () => {
2671
+ repeater.addRow();
2672
+ setFocusing({
2673
+ row: repeater.rowCount,
2674
+ parts: []
2675
+ });
2676
+ },
2600
2677
  children: addLabel
2601
2678
  }) : null
2602
2679
  ]
2603
2680
  });
2604
2681
  }
2605
- const DEFAULT_COMPONENTS = {
2606
- text: TextField,
2607
- textarea: TextareaField,
2608
- number: NumberSwitch,
2609
- checkbox: CheckboxField,
2610
- date: DateField,
2611
- time: TimeField,
2612
- datetime: DateTimeField,
2613
- select: SelectField,
2614
- radio: RadioGroupField,
2615
- selectboxes: SelectBoxesSwitch,
2616
- ranking: RankingField,
2617
- matrix: MatrixField,
2618
- file: FileField,
2619
- richtext: RichTextField,
2620
- signature: SignatureField,
2621
- hidden: null,
2622
- static: StaticField,
2623
- group: null,
2624
- page: null,
2625
- repeater: null
2626
- };
2627
2682
  //#endregion
2628
2683
  //#region src/error-summary.tsx
2629
2684
  function ErrorSummary({ labels }) {