@formancy/react 0.9.0 → 0.11.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.map +1 -1
- package/dist/index.mjs +277 -110
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.d.mts.map
CHANGED
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;wBCdP,aAAa,OAAO,oCAAiB,IAAA;;;UC7CpC;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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment, createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
|
-
import { cancelUploads, fieldUploads, formatPath, inOptionOrder, parsePath, placedGroup, placedPage, thumbnailSize, ticked } from "@formancy/core";
|
|
3
|
-
import { answersToReview, countSentence, createFormText, errorSentence, optionsStatus } from "@formancy/core/words";
|
|
2
|
+
import { cancelUploads, fieldUploads, formatPath, hasReadOnlyControl, inOptionOrder, parsePath, placedGroup, placedPage, thumbnailSize, ticked } from "@formancy/core";
|
|
3
|
+
import { answerInWords, answersToReview, countSentence, createFormText, errorSentence, optionsStatus } from "@formancy/core/words";
|
|
4
4
|
import { LAYOUT_LEAF_KINDS, acceptRemoteAnswer, answerFromText, applyRichCommand, capRemoteAnswer, datagridColumns, editMasked, formatMasked, isImageSource, layoutChildren, layoutNodeShows, maskIsNumeric, maskPlaceholder, narrowOptionsByLabel, parseRichText, resolveText, resolvedLocale } from "@formancy/spec";
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { flushSync } from "react-dom";
|
|
@@ -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
|
|
@@ -384,6 +384,16 @@ function placedPaths(nodes, into = []) {
|
|
|
384
384
|
return into;
|
|
385
385
|
}
|
|
386
386
|
//#endregion
|
|
387
|
+
//#region src/focus-row.ts
|
|
388
|
+
function focusRow(repeater, target, add) {
|
|
389
|
+
if (target === "add") return focusControl(add);
|
|
390
|
+
const row = repeater.querySelectorAll("[data-formancy-part=\"row\"], [data-formancy-part=\"datagrid-row\"]")[target.row];
|
|
391
|
+
if (row === void 0) return;
|
|
392
|
+
const part = target.parts.map((name) => row.querySelector(`[data-formancy-part="${name}"]`)).find((found) => found !== null);
|
|
393
|
+
const first = row.querySelector("input:not([type=\"hidden\"]):not(:disabled), select:not(:disabled), textarea:not(:disabled), button:not(:disabled), [contenteditable=\"true\"], [tabindex=\"0\"]");
|
|
394
|
+
focusControl(part ?? first);
|
|
395
|
+
}
|
|
396
|
+
//#endregion
|
|
387
397
|
//#region src/review.tsx
|
|
388
398
|
function SubmitButton({ submitLabel, onSubmit, onFailedNavigate }) {
|
|
389
399
|
const engine = useFormEngine();
|
|
@@ -464,7 +474,10 @@ function Review({ onChange, children }) {
|
|
|
464
474
|
"data-formancy-part": "review-answer",
|
|
465
475
|
children: [
|
|
466
476
|
/* @__PURE__ */ jsx("dt", { children: answer.label }),
|
|
467
|
-
/* @__PURE__ */ jsx("dd", {
|
|
477
|
+
/* @__PURE__ */ jsx("dd", {
|
|
478
|
+
...answer.answered ? {} : { "data-formancy-part": "review-unanswered" },
|
|
479
|
+
children: answer.said
|
|
480
|
+
}),
|
|
468
481
|
/* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsxs("button", {
|
|
469
482
|
type: "button",
|
|
470
483
|
onClick: () => onChange(answer),
|
|
@@ -556,7 +569,8 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
556
569
|
const debounceMs = source?.debounceMs ?? DEBOUNCE_MS;
|
|
557
570
|
const minQueryLength = source?.minQueryLength ?? MIN_QUERY;
|
|
558
571
|
const maxRows = source?.maxRows ?? MAX_ROWS;
|
|
559
|
-
const stored =
|
|
572
|
+
const stored = storedValues(field.value);
|
|
573
|
+
const storedKey = stored.join("\0");
|
|
560
574
|
const path = field.path;
|
|
561
575
|
const disabled = field.disabled;
|
|
562
576
|
const asked = useRef(0);
|
|
@@ -628,18 +642,19 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
628
642
|
]);
|
|
629
643
|
const askedFor = useRef(/* @__PURE__ */ new Set());
|
|
630
644
|
useEffect(() => {
|
|
631
|
-
if (!enabled || source === void 0 ||
|
|
632
|
-
|
|
633
|
-
|
|
645
|
+
if (!enabled || source === void 0 || storedKey === "") return;
|
|
646
|
+
const unnamed = storedKey.split("\0").filter((value) => !named.has(value) && !askedFor.current.has(value));
|
|
647
|
+
if (unnamed.length === 0) return;
|
|
648
|
+
for (const value of unnamed) askedFor.current.add(value);
|
|
634
649
|
const controller = new AbortController();
|
|
635
650
|
source.resolve({
|
|
636
651
|
kind: "labels",
|
|
637
652
|
source: name ?? "",
|
|
638
653
|
path,
|
|
639
654
|
query: "",
|
|
640
|
-
values:
|
|
655
|
+
values: unnamed,
|
|
641
656
|
locale: engine.locale(),
|
|
642
|
-
limit:
|
|
657
|
+
limit: unnamed.length,
|
|
643
658
|
signal: controller.signal
|
|
644
659
|
}).then((answer) => {
|
|
645
660
|
const accepted = acceptRemoteAnswer(answer);
|
|
@@ -656,7 +671,7 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
656
671
|
source,
|
|
657
672
|
name,
|
|
658
673
|
path,
|
|
659
|
-
|
|
674
|
+
storedKey,
|
|
660
675
|
named,
|
|
661
676
|
engine
|
|
662
677
|
]);
|
|
@@ -667,13 +682,11 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
667
682
|
})),
|
|
668
683
|
remote: null
|
|
669
684
|
};
|
|
670
|
-
const offered = [...rows];
|
|
671
|
-
if (stored !== void 0 && !offered.some((row) => row.value === stored)) offered.unshift({
|
|
672
|
-
value: stored,
|
|
673
|
-
label: named.get(stored) ?? stored
|
|
674
|
-
});
|
|
675
685
|
return {
|
|
676
|
-
options:
|
|
686
|
+
options: [...stored.filter((value) => !rows.some((row) => row.value === value)).map((value) => ({
|
|
687
|
+
value,
|
|
688
|
+
label: named.get(value) ?? value
|
|
689
|
+
})), ...rows],
|
|
677
690
|
remote: {
|
|
678
691
|
unavailable: source === void 0,
|
|
679
692
|
busy,
|
|
@@ -684,6 +697,11 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
684
697
|
}
|
|
685
698
|
};
|
|
686
699
|
}
|
|
700
|
+
function storedValues(value) {
|
|
701
|
+
if (typeof value === "string") return value === "" ? [] : [value];
|
|
702
|
+
if (!Array.isArray(value)) return [];
|
|
703
|
+
return value.filter((item) => typeof item === "string" && item !== "");
|
|
704
|
+
}
|
|
687
705
|
//#endregion
|
|
688
706
|
//#region src/uploads.ts
|
|
689
707
|
const UploaderContext = createContext(void 0);
|
|
@@ -773,6 +791,24 @@ function OptionPicture({ option }) {
|
|
|
773
791
|
}), " "] });
|
|
774
792
|
}
|
|
775
793
|
//#endregion
|
|
794
|
+
//#region src/fields/computed-answer.tsx
|
|
795
|
+
function ComputedAnswerField({ path, label }) {
|
|
796
|
+
const field = useField(path);
|
|
797
|
+
const engine = useFormEngine();
|
|
798
|
+
const text = useFormText();
|
|
799
|
+
return /* @__PURE__ */ jsx(FieldShell, {
|
|
800
|
+
path,
|
|
801
|
+
field,
|
|
802
|
+
label,
|
|
803
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
804
|
+
type: "text",
|
|
805
|
+
readOnly: true,
|
|
806
|
+
...field.controlProps,
|
|
807
|
+
value: answerInWords(engine.schema(), field.def, field.value, text, engine.locale())
|
|
808
|
+
})
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
//#endregion
|
|
776
812
|
//#region src/fields/typeahead-field.tsx
|
|
777
813
|
function TypeaheadSelectField({ path, label, field }) {
|
|
778
814
|
const text = useFormText();
|
|
@@ -961,6 +997,8 @@ function SelectField({ path, label }) {
|
|
|
961
997
|
label,
|
|
962
998
|
children: /* @__PURE__ */ jsx("p", {
|
|
963
999
|
"data-formancy-part": "options-unavailable",
|
|
1000
|
+
id: field.controlProps.id,
|
|
1001
|
+
tabIndex: -1,
|
|
964
1002
|
children: text("options.unavailable", { source: field.def.optionsSource ?? "" })
|
|
965
1003
|
})
|
|
966
1004
|
});
|
|
@@ -1057,6 +1095,8 @@ function FileField({ path, label }) {
|
|
|
1057
1095
|
children: [
|
|
1058
1096
|
upload === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
1059
1097
|
"data-formancy-part": "file-unavailable",
|
|
1098
|
+
id: field.controlProps.id,
|
|
1099
|
+
tabIndex: -1,
|
|
1060
1100
|
children: text("file.unavailable")
|
|
1061
1101
|
}) : /* @__PURE__ */ jsx("div", {
|
|
1062
1102
|
"data-formancy-part": "file-dropzone",
|
|
@@ -1474,14 +1514,30 @@ function SelectBoxesSwitch(props) {
|
|
|
1474
1514
|
function TagPickerField({ path, label }) {
|
|
1475
1515
|
const field = useField(path);
|
|
1476
1516
|
const text = useFormText();
|
|
1477
|
-
const options = useResolvedOptions(field);
|
|
1478
1517
|
const chosen = Array.isArray(field.value) ? field.value.map(String) : [];
|
|
1479
1518
|
const [query, setQuery] = useState("");
|
|
1480
1519
|
const [open, setOpen] = useState(false);
|
|
1481
1520
|
const listboxId = `${field.ids.control}:listbox`;
|
|
1521
|
+
const written = useResolvedOptions(field);
|
|
1522
|
+
const sourced = useSourcedOptions({
|
|
1523
|
+
...field,
|
|
1524
|
+
path
|
|
1525
|
+
}, query);
|
|
1526
|
+
const remote = sourced.remote;
|
|
1527
|
+
const options = remote === null ? written : sourced.options;
|
|
1482
1528
|
const remaining = options.filter((option) => !chosen.includes(option.value));
|
|
1483
|
-
const matches = narrowOptionsByLabel(remaining, query);
|
|
1529
|
+
const matches = remote === null ? narrowOptionsByLabel(remaining, query) : remaining;
|
|
1484
1530
|
const expanded = open && !field.disabled && matches.length > 0;
|
|
1531
|
+
const said = remote === null ? "" : optionsStatus(text, {
|
|
1532
|
+
control: "typeahead",
|
|
1533
|
+
open: open && !field.disabled,
|
|
1534
|
+
disabled: field.disabled,
|
|
1535
|
+
failed: remote.failed,
|
|
1536
|
+
busy: remote.busy,
|
|
1537
|
+
needs: remote.needs,
|
|
1538
|
+
query,
|
|
1539
|
+
completeness: remote.completeness
|
|
1540
|
+
});
|
|
1485
1541
|
const add = (value) => {
|
|
1486
1542
|
field.setValue(ticked(field.def, chosen, value, true));
|
|
1487
1543
|
setQuery("");
|
|
@@ -1491,92 +1547,141 @@ function TagPickerField({ path, label }) {
|
|
|
1491
1547
|
field.setValue(chosen.filter((candidate) => candidate !== value));
|
|
1492
1548
|
};
|
|
1493
1549
|
const labelFor = (value) => options.find((option) => option.value === value)?.label ?? value;
|
|
1550
|
+
if (remote?.unavailable === true) return /* @__PURE__ */ jsx(FieldShell, {
|
|
1551
|
+
path,
|
|
1552
|
+
field,
|
|
1553
|
+
label,
|
|
1554
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
1555
|
+
"data-formancy-part": "options-unavailable",
|
|
1556
|
+
id: field.controlProps.id,
|
|
1557
|
+
tabIndex: -1,
|
|
1558
|
+
children: text("options.unavailable", { source: field.def.optionsSource ?? "" })
|
|
1559
|
+
})
|
|
1560
|
+
});
|
|
1494
1561
|
return /* @__PURE__ */ jsx(FieldShell, {
|
|
1495
1562
|
path,
|
|
1496
1563
|
field,
|
|
1497
1564
|
label,
|
|
1498
1565
|
children: /* @__PURE__ */ jsxs("div", {
|
|
1499
1566
|
"data-formancy-part": "tagpicker",
|
|
1500
|
-
children: [
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1567
|
+
children: [
|
|
1568
|
+
chosen.length === 0 ? null : /* @__PURE__ */ jsx("ul", {
|
|
1569
|
+
"data-formancy-part": "tagpicker-chips",
|
|
1570
|
+
"aria-label": text("tagpicker.chosen", { label }),
|
|
1571
|
+
children: chosen.map((value) => /* @__PURE__ */ jsxs("li", {
|
|
1572
|
+
"data-formancy-part": "tagpicker-chip",
|
|
1573
|
+
children: [labelFor(value), /* @__PURE__ */ jsx("button", {
|
|
1574
|
+
type: "button",
|
|
1575
|
+
"data-formancy-part": "tagpicker-remove",
|
|
1576
|
+
"aria-label": text("tagpicker.remove", { option: labelFor(value) }),
|
|
1577
|
+
disabled: field.disabled,
|
|
1578
|
+
onClick: () => remove(value),
|
|
1579
|
+
children: "×"
|
|
1580
|
+
})]
|
|
1581
|
+
}, value))
|
|
1582
|
+
}),
|
|
1583
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1584
|
+
"data-formancy-part": "tagpicker-anchor",
|
|
1585
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
1586
|
+
type: "text",
|
|
1587
|
+
role: "combobox",
|
|
1588
|
+
...field.controlProps,
|
|
1589
|
+
"data-formancy-part": "tagpicker-input",
|
|
1590
|
+
autoComplete: "off",
|
|
1591
|
+
"aria-expanded": expanded,
|
|
1592
|
+
"aria-controls": listboxId,
|
|
1593
|
+
"aria-autocomplete": "list",
|
|
1594
|
+
"aria-busy": remote?.busy === true ? true : void 0,
|
|
1595
|
+
value: query,
|
|
1596
|
+
onChange: (event) => {
|
|
1597
|
+
setQuery(event.target.value);
|
|
1598
|
+
setOpen(true);
|
|
1599
|
+
},
|
|
1600
|
+
onClick: () => setOpen(true),
|
|
1601
|
+
onKeyDown: (event) => {
|
|
1602
|
+
if (event.key === "Escape") {
|
|
1603
|
+
setOpen(false);
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (event.key === "Enter" && expanded && matches[0] !== void 0) {
|
|
1607
|
+
event.preventDefault();
|
|
1608
|
+
add(matches[0].value);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (event.key === "Backspace" && query === "" && chosen.length > 0) remove(chosen[chosen.length - 1]);
|
|
1612
|
+
},
|
|
1613
|
+
onBlur: () => {
|
|
1533
1614
|
setOpen(false);
|
|
1534
|
-
|
|
1615
|
+
field.touch();
|
|
1535
1616
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
children: option.label
|
|
1560
|
-
}, option.value))
|
|
1561
|
-
})]
|
|
1562
|
-
})]
|
|
1617
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
1618
|
+
id: listboxId,
|
|
1619
|
+
role: "listbox",
|
|
1620
|
+
"aria-label": text("options.suggestions", { label }),
|
|
1621
|
+
"data-formancy-part": "tagpicker-listbox",
|
|
1622
|
+
hidden: !expanded,
|
|
1623
|
+
children: matches.map((option) => /* @__PURE__ */ jsx("li", {
|
|
1624
|
+
role: "option",
|
|
1625
|
+
"data-formancy-part": "tagpicker-option",
|
|
1626
|
+
"aria-selected": false,
|
|
1627
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1628
|
+
onClick: () => add(option.value),
|
|
1629
|
+
children: option.label
|
|
1630
|
+
}, option.value))
|
|
1631
|
+
})]
|
|
1632
|
+
}),
|
|
1633
|
+
remote === null ? null : /* @__PURE__ */ jsx("p", {
|
|
1634
|
+
role: "status",
|
|
1635
|
+
"data-formancy-part": "tagpicker-status",
|
|
1636
|
+
"data-state": remote.failed ? "failed" : void 0,
|
|
1637
|
+
children: said
|
|
1638
|
+
})
|
|
1639
|
+
]
|
|
1563
1640
|
})
|
|
1564
1641
|
});
|
|
1565
1642
|
}
|
|
1566
1643
|
function SelectBoxesField({ path, label }) {
|
|
1567
1644
|
const field = useField(path);
|
|
1568
|
-
const
|
|
1645
|
+
const text = useFormText();
|
|
1646
|
+
const written = useResolvedOptions(field);
|
|
1647
|
+
const sourced = useSourcedOptions({
|
|
1648
|
+
...field,
|
|
1649
|
+
path
|
|
1650
|
+
}, "");
|
|
1651
|
+
const remote = sourced.remote;
|
|
1652
|
+
const options = remote === null ? written : sourced.options;
|
|
1569
1653
|
const showError = field.touched && field.errors.length > 0;
|
|
1570
1654
|
const chosen = Array.isArray(field.value) ? field.value : [];
|
|
1655
|
+
const group = useRef(null);
|
|
1656
|
+
const again = useRef(null);
|
|
1657
|
+
const [retrying, setRetrying] = useState(false);
|
|
1658
|
+
const settled = remote !== null && !remote.busy && !remote.failed;
|
|
1659
|
+
useEffect(() => {
|
|
1660
|
+
if (!retrying || !settled) return;
|
|
1661
|
+
setRetrying(false);
|
|
1662
|
+
if (document.activeElement === again.current) group.current?.querySelector("input[type=\"checkbox\"]")?.focus();
|
|
1663
|
+
}, [retrying, settled]);
|
|
1664
|
+
const said = remote === null ? "" : optionsStatus(text, {
|
|
1665
|
+
control: "ticks",
|
|
1666
|
+
open: true,
|
|
1667
|
+
disabled: field.disabled,
|
|
1668
|
+
failed: remote.failed,
|
|
1669
|
+
busy: remote.busy,
|
|
1670
|
+
needs: remote.needs,
|
|
1671
|
+
query: "",
|
|
1672
|
+
completeness: remote.completeness
|
|
1673
|
+
});
|
|
1571
1674
|
const toggle = (value, on) => {
|
|
1572
1675
|
field.setValue(ticked(field.def, chosen, value, on));
|
|
1573
1676
|
};
|
|
1574
1677
|
return /* @__PURE__ */ jsxs("fieldset", {
|
|
1678
|
+
ref: group,
|
|
1575
1679
|
id: field.ids.control,
|
|
1576
1680
|
"data-formancy-part": "field",
|
|
1577
1681
|
"data-formancy-field-path": path,
|
|
1578
1682
|
"data-state": showError ? "invalid" : "valid",
|
|
1579
1683
|
"aria-describedby": field.controlProps["aria-describedby"],
|
|
1684
|
+
"aria-busy": remote?.busy === true ? true : void 0,
|
|
1580
1685
|
children: [
|
|
1581
1686
|
/* @__PURE__ */ jsx("legend", {
|
|
1582
1687
|
"data-formancy-part": "label",
|
|
@@ -1584,6 +1689,11 @@ function SelectBoxesField({ path, label }) {
|
|
|
1584
1689
|
}),
|
|
1585
1690
|
/* @__PURE__ */ jsx(RequiredHint, { field }),
|
|
1586
1691
|
/* @__PURE__ */ jsx(Description, { field }),
|
|
1692
|
+
remote?.unavailable === true ? /* @__PURE__ */ jsx("p", {
|
|
1693
|
+
"data-formancy-part": "options-unavailable",
|
|
1694
|
+
tabIndex: -1,
|
|
1695
|
+
children: text("options.unavailable", { source: field.def.optionsSource ?? "" })
|
|
1696
|
+
}) : null,
|
|
1587
1697
|
options.map((option) => {
|
|
1588
1698
|
const optionId = `${field.ids.control}:${option.value}`;
|
|
1589
1699
|
return /* @__PURE__ */ jsxs("span", {
|
|
@@ -1603,6 +1713,22 @@ function SelectBoxesField({ path, label }) {
|
|
|
1603
1713
|
})]
|
|
1604
1714
|
}, option.value);
|
|
1605
1715
|
}),
|
|
1716
|
+
remote === null || remote.unavailable ? null : /* @__PURE__ */ jsx("p", {
|
|
1717
|
+
role: "status",
|
|
1718
|
+
"data-formancy-part": "checkbox-status",
|
|
1719
|
+
"data-state": remote.failed ? "failed" : void 0,
|
|
1720
|
+
children: said
|
|
1721
|
+
}),
|
|
1722
|
+
remote !== null && (remote.failed || retrying) && !field.disabled ? /* @__PURE__ */ jsx("button", {
|
|
1723
|
+
ref: again,
|
|
1724
|
+
type: "button",
|
|
1725
|
+
"data-formancy-part": "checkbox-retry",
|
|
1726
|
+
onClick: () => {
|
|
1727
|
+
setRetrying(true);
|
|
1728
|
+
remote.retry();
|
|
1729
|
+
},
|
|
1730
|
+
children: text("options.retry")
|
|
1731
|
+
}) : null,
|
|
1606
1732
|
showError ? /* @__PURE__ */ jsx(ErrorText, { field }) : null
|
|
1607
1733
|
]
|
|
1608
1734
|
});
|
|
@@ -2209,6 +2335,30 @@ function TextareaField({ path, label }) {
|
|
|
2209
2335
|
});
|
|
2210
2336
|
}
|
|
2211
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
|
|
2212
2362
|
//#region src/form.tsx
|
|
2213
2363
|
function FormancyForm(props) {
|
|
2214
2364
|
return useFormEngine().wizard() === void 0 ? /* @__PURE__ */ jsx(FlatForm, { ...props }) : /* @__PURE__ */ jsx(PagedForm, { ...props });
|
|
@@ -2324,7 +2474,7 @@ function FieldList({ labels, registry, page, layout }) {
|
|
|
2324
2474
|
function FieldSlot({ path, fallbackLabel, registry }) {
|
|
2325
2475
|
const field = useField(path);
|
|
2326
2476
|
if (!field.visible) return null;
|
|
2327
|
-
const Component = registry?.byPath?.[path] ?? registry?.byType?.[field.type] ?? DEFAULT_COMPONENTS[field.type];
|
|
2477
|
+
const Component = registry?.byPath?.[path] ?? (field.readOnly && !hasReadOnlyControl(field.def) ? ComputedAnswerField : void 0) ?? registry?.byType?.[field.type] ?? DEFAULT_COMPONENTS[field.type];
|
|
2328
2478
|
if (Component === null) return null;
|
|
2329
2479
|
const label = field.label ?? fallbackLabel ?? path;
|
|
2330
2480
|
return /* @__PURE__ */ jsx(Component, {
|
|
@@ -2379,6 +2529,14 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2379
2529
|
const label = engine.text(def?.label) ?? labels?.[wire] ?? wire;
|
|
2380
2530
|
const addLabel = def?.addLabel ?? text("repeater.add", { label });
|
|
2381
2531
|
const removeLabel = def?.removeLabel ?? text("repeater.remove", { label });
|
|
2532
|
+
const fieldset = useRef(null);
|
|
2533
|
+
const addButton = useRef(null);
|
|
2534
|
+
const [focusing, setFocusing] = useState(void 0);
|
|
2535
|
+
useEffect(() => {
|
|
2536
|
+
if (focusing === void 0 || fieldset.current === null) return;
|
|
2537
|
+
focusRow(fieldset.current, focusing, addButton.current);
|
|
2538
|
+
setFocusing(void 0);
|
|
2539
|
+
}, [focusing]);
|
|
2382
2540
|
const fallbackFor = (instanceWire) => {
|
|
2383
2541
|
const template = instanceWire.replace(/\[\d+\]/, "[]");
|
|
2384
2542
|
return labels?.[template] ?? labels?.[instanceWire];
|
|
@@ -2397,7 +2555,13 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2397
2555
|
type: "button",
|
|
2398
2556
|
"data-formancy-part": "row-remove",
|
|
2399
2557
|
disabled: field.disabled,
|
|
2400
|
-
onClick: () =>
|
|
2558
|
+
onClick: () => {
|
|
2559
|
+
repeater.removeRow(index);
|
|
2560
|
+
setFocusing(repeater.rowCount === 1 ? "add" : {
|
|
2561
|
+
row: Math.min(index, repeater.rowCount - 2),
|
|
2562
|
+
parts: []
|
|
2563
|
+
});
|
|
2564
|
+
},
|
|
2401
2565
|
children: /* @__PURE__ */ jsx("span", {
|
|
2402
2566
|
"data-formancy-part": "row-action-text",
|
|
2403
2567
|
children: text("repeater.removeRow", {
|
|
@@ -2407,11 +2571,17 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2407
2571
|
})
|
|
2408
2572
|
})
|
|
2409
2573
|
}) : null,
|
|
2410
|
-
index > 0 ? /* @__PURE__ */ jsx("button", {
|
|
2574
|
+
index > 0 && !field.readOnly ? /* @__PURE__ */ jsx("button", {
|
|
2411
2575
|
type: "button",
|
|
2412
2576
|
"data-formancy-part": "row-up",
|
|
2413
2577
|
disabled: field.disabled,
|
|
2414
|
-
onClick: () =>
|
|
2578
|
+
onClick: () => {
|
|
2579
|
+
repeater.moveRow(index, index - 1);
|
|
2580
|
+
setFocusing({
|
|
2581
|
+
row: index - 1,
|
|
2582
|
+
parts: ["row-up", "row-down"]
|
|
2583
|
+
});
|
|
2584
|
+
},
|
|
2415
2585
|
children: /* @__PURE__ */ jsx("span", {
|
|
2416
2586
|
"data-formancy-part": "row-action-text",
|
|
2417
2587
|
children: text("repeater.moveUp", {
|
|
@@ -2421,11 +2591,17 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2421
2591
|
})
|
|
2422
2592
|
})
|
|
2423
2593
|
}) : null,
|
|
2424
|
-
index < repeater.rowCount - 1 ? /* @__PURE__ */ jsx("button", {
|
|
2594
|
+
index < repeater.rowCount - 1 && !field.readOnly ? /* @__PURE__ */ jsx("button", {
|
|
2425
2595
|
type: "button",
|
|
2426
2596
|
"data-formancy-part": "row-down",
|
|
2427
2597
|
disabled: field.disabled,
|
|
2428
|
-
onClick: () =>
|
|
2598
|
+
onClick: () => {
|
|
2599
|
+
repeater.moveRow(index, index + 1);
|
|
2600
|
+
setFocusing({
|
|
2601
|
+
row: index + 1,
|
|
2602
|
+
parts: ["row-down", "row-up"]
|
|
2603
|
+
});
|
|
2604
|
+
},
|
|
2429
2605
|
children: /* @__PURE__ */ jsx("span", {
|
|
2430
2606
|
"data-formancy-part": "row-action-text",
|
|
2431
2607
|
children: text("repeater.moveDown", {
|
|
@@ -2458,6 +2634,7 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2458
2634
|
if (!field.visible) return null;
|
|
2459
2635
|
const showError = field.touched && field.errors.length > 0;
|
|
2460
2636
|
return /* @__PURE__ */ jsxs("fieldset", {
|
|
2637
|
+
ref: fieldset,
|
|
2461
2638
|
"data-formancy-part": "repeater",
|
|
2462
2639
|
"data-formancy-field-path": wire,
|
|
2463
2640
|
"data-state": showError ? "invalid" : "valid",
|
|
@@ -2484,37 +2661,27 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2484
2661
|
}) : null, rows]
|
|
2485
2662
|
}) : rows,
|
|
2486
2663
|
showError ? /* @__PURE__ */ jsx(ErrorText, { field }) : null,
|
|
2664
|
+
def?.maxItems === void 0 || field.readOnly ? null : /* @__PURE__ */ jsx("p", {
|
|
2665
|
+
role: "status",
|
|
2666
|
+
"data-formancy-part": "repeater-status",
|
|
2667
|
+
children: repeater.canAdd ? "" : text("repeater.full", { max: def.maxItems })
|
|
2668
|
+
}),
|
|
2487
2669
|
repeater.canAdd ? /* @__PURE__ */ jsx("button", {
|
|
2488
2670
|
type: "button",
|
|
2671
|
+
ref: addButton,
|
|
2489
2672
|
disabled: field.disabled,
|
|
2490
|
-
onClick: () =>
|
|
2673
|
+
onClick: () => {
|
|
2674
|
+
repeater.addRow();
|
|
2675
|
+
setFocusing({
|
|
2676
|
+
row: repeater.rowCount,
|
|
2677
|
+
parts: []
|
|
2678
|
+
});
|
|
2679
|
+
},
|
|
2491
2680
|
children: addLabel
|
|
2492
2681
|
}) : null
|
|
2493
2682
|
]
|
|
2494
2683
|
});
|
|
2495
2684
|
}
|
|
2496
|
-
const DEFAULT_COMPONENTS = {
|
|
2497
|
-
text: TextField,
|
|
2498
|
-
textarea: TextareaField,
|
|
2499
|
-
number: NumberSwitch,
|
|
2500
|
-
checkbox: CheckboxField,
|
|
2501
|
-
date: DateField,
|
|
2502
|
-
time: TimeField,
|
|
2503
|
-
datetime: DateTimeField,
|
|
2504
|
-
select: SelectField,
|
|
2505
|
-
radio: RadioGroupField,
|
|
2506
|
-
selectboxes: SelectBoxesSwitch,
|
|
2507
|
-
ranking: RankingField,
|
|
2508
|
-
matrix: MatrixField,
|
|
2509
|
-
file: FileField,
|
|
2510
|
-
richtext: RichTextField,
|
|
2511
|
-
signature: SignatureField,
|
|
2512
|
-
hidden: null,
|
|
2513
|
-
static: StaticField,
|
|
2514
|
-
group: null,
|
|
2515
|
-
page: null,
|
|
2516
|
-
repeater: null
|
|
2517
|
-
};
|
|
2518
2685
|
//#endregion
|
|
2519
2686
|
//#region src/error-summary.tsx
|
|
2520
2687
|
function ErrorSummary({ labels }) {
|