@marktiderman/genesis-forms-web 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -1
- package/dist/index.d.ts +91 -2
- package/dist/index.js +342 -20
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { createContext, useState, useEffect, useRef, useId, useContext, useMemo,
|
|
|
3
3
|
import { Button } from '@marktiderman/genesis-ui/button';
|
|
4
4
|
import { Progress } from '@marktiderman/genesis-ui/progress';
|
|
5
5
|
import { cn } from '@marktiderman/genesis-ui/utils';
|
|
6
|
-
import { ratingDisplay,
|
|
7
|
-
import { createRendererRegistry, useFormSelector, useForm, FormRuntime, useFormContext, FieldRenderer } from '@marktiderman/genesis-forms-react';
|
|
6
|
+
import { ratingDisplay, numberLimits, dateLimits, listLimits, tableColumns, tableLimits, parseTimestamp, formatTimestamp, formatCalculated, photoLimits, photoRefs, getFieldType, scaleLabels, isSearchable, otherOption, choiceOptions, searchOptions, OTHER_MAX_LENGTH, photoCountMessage, isPhotoRef, photoTypeMessage, photoSizeMessage, respondentPath, formProgress } from '@marktiderman/genesis-forms';
|
|
7
|
+
import { createRendererRegistry, useFormSelector, useChoiceOptions, useForm, FormRuntime, useFormContext, FieldRenderer } from '@marktiderman/genesis-forms-react';
|
|
8
8
|
import { Checkbox } from '@marktiderman/genesis-ui/checkbox';
|
|
9
9
|
import { Input } from '@marktiderman/genesis-ui/input';
|
|
10
10
|
import { Label } from '@marktiderman/genesis-ui/label';
|
|
@@ -15,6 +15,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
|
15
15
|
import { Stack } from '@marktiderman/genesis-ui/layout';
|
|
16
16
|
import { SortableList } from '@marktiderman/genesis-ui/sortable-list';
|
|
17
17
|
import { Spinner } from '@marktiderman/genesis-ui/spinner';
|
|
18
|
+
import { Command, CommandInput, CommandList, CommandEmpty, CommandItem } from '@marktiderman/genesis-ui/command';
|
|
18
19
|
|
|
19
20
|
// src/Form.tsx
|
|
20
21
|
var FormPresentationContext = createContext({ presentation: "embedded", layout: "classic" });
|
|
@@ -1004,6 +1005,127 @@ function PhotoField(props) {
|
|
|
1004
1005
|
] }) });
|
|
1005
1006
|
}
|
|
1006
1007
|
var photoRenderers = [{ type: "photo", Component: PhotoField }];
|
|
1008
|
+
function OptionImage({ src, large }) {
|
|
1009
|
+
return /* @__PURE__ */ jsx(
|
|
1010
|
+
"img",
|
|
1011
|
+
{
|
|
1012
|
+
src,
|
|
1013
|
+
alt: "",
|
|
1014
|
+
loading: "lazy",
|
|
1015
|
+
"data-gf-option-image": "",
|
|
1016
|
+
className: cn("shrink-0 rounded-md bg-muted object-cover", large ? "size-12" : "size-10")
|
|
1017
|
+
}
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
function OptionText({ label, description }) {
|
|
1021
|
+
return /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
1022
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
1023
|
+
description ? /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", "data-gf-option-description": "", children: description }) : null
|
|
1024
|
+
] });
|
|
1025
|
+
}
|
|
1026
|
+
function CheckMark({ on }) {
|
|
1027
|
+
return /* @__PURE__ */ jsx(
|
|
1028
|
+
"svg",
|
|
1029
|
+
{
|
|
1030
|
+
viewBox: "0 0 24 24",
|
|
1031
|
+
fill: "none",
|
|
1032
|
+
stroke: "currentColor",
|
|
1033
|
+
strokeWidth: "2.5",
|
|
1034
|
+
strokeLinecap: "round",
|
|
1035
|
+
strokeLinejoin: "round",
|
|
1036
|
+
"aria-hidden": "true",
|
|
1037
|
+
className: cn("size-4 shrink-0 text-primary", on ? "opacity-100" : "opacity-0"),
|
|
1038
|
+
children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
|
|
1039
|
+
}
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
var SEARCH_OTHER_VALUE = "\0other";
|
|
1043
|
+
function SearchableOptions({
|
|
1044
|
+
props,
|
|
1045
|
+
options,
|
|
1046
|
+
selected,
|
|
1047
|
+
multiple,
|
|
1048
|
+
otherLabel,
|
|
1049
|
+
otherOn,
|
|
1050
|
+
onPick
|
|
1051
|
+
}) {
|
|
1052
|
+
const { binding, inputId } = props;
|
|
1053
|
+
const [query, setQuery] = useState("");
|
|
1054
|
+
const { presentation } = useFormPresentation();
|
|
1055
|
+
const page = presentation === "page";
|
|
1056
|
+
const shown = searchOptions(options, query);
|
|
1057
|
+
const chosen = options.filter((o) => selected.includes(o.value));
|
|
1058
|
+
const labelOf = (o) => o.label ?? o.value;
|
|
1059
|
+
return /* @__PURE__ */ jsxs(
|
|
1060
|
+
"div",
|
|
1061
|
+
{
|
|
1062
|
+
className: "space-y-2",
|
|
1063
|
+
"data-gf-searchable": "",
|
|
1064
|
+
onKeyDown: (e) => {
|
|
1065
|
+
if (e.key === "Enter" && e.target.tagName === "INPUT") e.preventDefault();
|
|
1066
|
+
},
|
|
1067
|
+
children: [
|
|
1068
|
+
/* @__PURE__ */ jsxs(Command, { shouldFilter: false, label: binding.label, className: "rounded-md border border-input bg-background", children: [
|
|
1069
|
+
/* @__PURE__ */ jsx(
|
|
1070
|
+
CommandInput,
|
|
1071
|
+
{
|
|
1072
|
+
id: inputId,
|
|
1073
|
+
"data-gf-focus": "",
|
|
1074
|
+
value: query,
|
|
1075
|
+
onValueChange: setQuery,
|
|
1076
|
+
placeholder: "Type to search",
|
|
1077
|
+
"aria-label": `Search: ${binding.label}`,
|
|
1078
|
+
"aria-invalid": binding.error ? true : void 0,
|
|
1079
|
+
"aria-required": binding.field.required || void 0,
|
|
1080
|
+
className: cn(page && "text-base")
|
|
1081
|
+
}
|
|
1082
|
+
),
|
|
1083
|
+
/* @__PURE__ */ jsxs(CommandList, { className: "max-h-72", "aria-multiselectable": multiple || void 0, children: [
|
|
1084
|
+
/* @__PURE__ */ jsx(CommandEmpty, { children: "No matches." }),
|
|
1085
|
+
shown.map((o) => {
|
|
1086
|
+
const on = selected.includes(o.value);
|
|
1087
|
+
return /* @__PURE__ */ jsxs(
|
|
1088
|
+
CommandItem,
|
|
1089
|
+
{
|
|
1090
|
+
value: o.value,
|
|
1091
|
+
keywords: [labelOf(o)],
|
|
1092
|
+
"aria-checked": on,
|
|
1093
|
+
onSelect: () => onPick(o.value),
|
|
1094
|
+
className: cn("min-h-11 gap-3 text-base", page && "min-h-12"),
|
|
1095
|
+
"data-gf-option": o.value,
|
|
1096
|
+
children: [
|
|
1097
|
+
/* @__PURE__ */ jsx(CheckMark, { on }),
|
|
1098
|
+
o.image ? /* @__PURE__ */ jsx(OptionImage, { src: o.image }) : null,
|
|
1099
|
+
/* @__PURE__ */ jsx(OptionText, { label: labelOf(o), description: o.description })
|
|
1100
|
+
]
|
|
1101
|
+
},
|
|
1102
|
+
o.value
|
|
1103
|
+
);
|
|
1104
|
+
}),
|
|
1105
|
+
otherLabel ? /* @__PURE__ */ jsxs(
|
|
1106
|
+
CommandItem,
|
|
1107
|
+
{
|
|
1108
|
+
value: SEARCH_OTHER_VALUE,
|
|
1109
|
+
"aria-checked": !!otherOn,
|
|
1110
|
+
onSelect: () => onPick(SEARCH_OTHER_VALUE),
|
|
1111
|
+
className: cn("min-h-11 gap-3 text-base", page && "min-h-12"),
|
|
1112
|
+
children: [
|
|
1113
|
+
/* @__PURE__ */ jsx(CheckMark, { on: !!otherOn }),
|
|
1114
|
+
/* @__PURE__ */ jsx(OptionText, { label: otherLabel })
|
|
1115
|
+
]
|
|
1116
|
+
}
|
|
1117
|
+
) : null
|
|
1118
|
+
] })
|
|
1119
|
+
] }),
|
|
1120
|
+
chosen.length ? /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", "aria-live": "polite", "data-gf-chosen": "", children: [
|
|
1121
|
+
multiple ? "Chosen" : "Your choice",
|
|
1122
|
+
": ",
|
|
1123
|
+
chosen.map(labelOf).join(", ")
|
|
1124
|
+
] }) : null
|
|
1125
|
+
]
|
|
1126
|
+
}
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1007
1129
|
function groupAria(props) {
|
|
1008
1130
|
return {
|
|
1009
1131
|
"aria-labelledby": `${props.inputId}-label`,
|
|
@@ -1278,8 +1400,19 @@ function useChoiceLook(inputId, count) {
|
|
|
1278
1400
|
} : void 0
|
|
1279
1401
|
};
|
|
1280
1402
|
}
|
|
1281
|
-
function ChoiceContent({
|
|
1282
|
-
|
|
1403
|
+
function ChoiceContent({
|
|
1404
|
+
look,
|
|
1405
|
+
index,
|
|
1406
|
+
label,
|
|
1407
|
+
description,
|
|
1408
|
+
image
|
|
1409
|
+
}) {
|
|
1410
|
+
if (!look.pageFocus) {
|
|
1411
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1412
|
+
image ? /* @__PURE__ */ jsx(OptionImage, { src: image }) : null,
|
|
1413
|
+
/* @__PURE__ */ jsx(OptionText, { label, description })
|
|
1414
|
+
] });
|
|
1415
|
+
}
|
|
1283
1416
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1284
1417
|
/* @__PURE__ */ jsx(
|
|
1285
1418
|
"span",
|
|
@@ -1289,7 +1422,8 @@ function ChoiceContent({ look, index, label }) {
|
|
|
1289
1422
|
children: KEYS[index] ?? ""
|
|
1290
1423
|
}
|
|
1291
1424
|
),
|
|
1292
|
-
/* @__PURE__ */ jsx(
|
|
1425
|
+
image ? /* @__PURE__ */ jsx(OptionImage, { src: image, large: true }) : null,
|
|
1426
|
+
/* @__PURE__ */ jsx(OptionText, { label, description }),
|
|
1293
1427
|
/* @__PURE__ */ jsx(
|
|
1294
1428
|
"svg",
|
|
1295
1429
|
{
|
|
@@ -1335,13 +1469,115 @@ function useOtherText(typed) {
|
|
|
1335
1469
|
const [text, setText] = useState(typed);
|
|
1336
1470
|
return { on, setOn, text, setText };
|
|
1337
1471
|
}
|
|
1472
|
+
function SearchableSingleField(props) {
|
|
1473
|
+
const { binding, onAdvance } = props;
|
|
1474
|
+
const options = useChoiceOptions(props.engine, binding.field.id);
|
|
1475
|
+
const other = otherOption(binding.field);
|
|
1476
|
+
const current = typeof binding.value === "string" ? binding.value : "";
|
|
1477
|
+
const typed = other && current && !choiceOptions(binding.field).some((o) => o.value === current) ? current : "";
|
|
1478
|
+
const otherText = useOtherText(typed);
|
|
1479
|
+
const textId = `${props.inputId}-other-text`;
|
|
1480
|
+
return /* @__PURE__ */ jsxs(FieldChrome, { props, children: [
|
|
1481
|
+
/* @__PURE__ */ jsx(
|
|
1482
|
+
SearchableOptions,
|
|
1483
|
+
{
|
|
1484
|
+
props,
|
|
1485
|
+
options,
|
|
1486
|
+
selected: otherText.on ? [] : [current],
|
|
1487
|
+
multiple: false,
|
|
1488
|
+
otherLabel: other?.label,
|
|
1489
|
+
otherOn: otherText.on,
|
|
1490
|
+
onPick: (v) => {
|
|
1491
|
+
if (v === SEARCH_OTHER_VALUE) {
|
|
1492
|
+
otherText.setOn(true);
|
|
1493
|
+
binding.setValue(otherText.text.trim() ? otherText.text : void 0);
|
|
1494
|
+
focusSoon(textId);
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
otherText.setOn(false);
|
|
1498
|
+
binding.setValue(v);
|
|
1499
|
+
onAdvance?.();
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
),
|
|
1503
|
+
other && otherText.on ? /* @__PURE__ */ jsx(
|
|
1504
|
+
OtherText,
|
|
1505
|
+
{
|
|
1506
|
+
id: textId,
|
|
1507
|
+
label: other.label,
|
|
1508
|
+
value: otherText.text,
|
|
1509
|
+
invalid: !!binding.error,
|
|
1510
|
+
onChange: (text) => {
|
|
1511
|
+
otherText.setText(text);
|
|
1512
|
+
binding.setValue(text.trim() ? text : void 0);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
) : null
|
|
1516
|
+
] });
|
|
1517
|
+
}
|
|
1518
|
+
function SearchableMultiField(props) {
|
|
1519
|
+
const { binding } = props;
|
|
1520
|
+
const options = useChoiceOptions(props.engine, binding.field.id);
|
|
1521
|
+
const other = otherOption(binding.field);
|
|
1522
|
+
const selected = Array.isArray(binding.value) ? binding.value : [];
|
|
1523
|
+
const known = new Set(choiceOptions(binding.field).map((o) => o.value));
|
|
1524
|
+
const chosen = selected.filter((v) => known.has(v));
|
|
1525
|
+
const otherText = useOtherText(other ? selected.find((v) => !known.has(v)) ?? "" : "");
|
|
1526
|
+
const textId = `${props.inputId}-other-text`;
|
|
1527
|
+
const commit = (picked, otherOn, text) => {
|
|
1528
|
+
const next = otherOn && text.trim() ? [...picked, text] : picked;
|
|
1529
|
+
binding.setValue(next.length ? next : void 0);
|
|
1530
|
+
};
|
|
1531
|
+
return /* @__PURE__ */ jsxs(FieldChrome, { props, children: [
|
|
1532
|
+
/* @__PURE__ */ jsx(
|
|
1533
|
+
SearchableOptions,
|
|
1534
|
+
{
|
|
1535
|
+
props,
|
|
1536
|
+
options,
|
|
1537
|
+
selected: chosen,
|
|
1538
|
+
multiple: true,
|
|
1539
|
+
otherLabel: other?.label,
|
|
1540
|
+
otherOn: otherText.on,
|
|
1541
|
+
onPick: (v) => {
|
|
1542
|
+
if (v === SEARCH_OTHER_VALUE) {
|
|
1543
|
+
const on = !otherText.on;
|
|
1544
|
+
otherText.setOn(on);
|
|
1545
|
+
commit(chosen, on, otherText.text);
|
|
1546
|
+
if (on) focusSoon(textId);
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
commit(chosen.includes(v) ? chosen.filter((x) => x !== v) : [...chosen, v], otherText.on, otherText.text);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
),
|
|
1553
|
+
other && otherText.on ? /* @__PURE__ */ jsx(
|
|
1554
|
+
OtherText,
|
|
1555
|
+
{
|
|
1556
|
+
id: textId,
|
|
1557
|
+
label: other.label,
|
|
1558
|
+
value: otherText.text,
|
|
1559
|
+
invalid: !!binding.error,
|
|
1560
|
+
onChange: (text) => {
|
|
1561
|
+
otherText.setText(text);
|
|
1562
|
+
commit(chosen, true, text);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
) : null
|
|
1566
|
+
] });
|
|
1567
|
+
}
|
|
1568
|
+
function useSearchable(props) {
|
|
1569
|
+
return isSearchable(props.binding.field);
|
|
1570
|
+
}
|
|
1338
1571
|
function SingleChoiceField(props) {
|
|
1572
|
+
return useSearchable(props) ? /* @__PURE__ */ jsx(SearchableSingleField, { ...props }) : /* @__PURE__ */ jsx(SingleChoiceList, { ...props });
|
|
1573
|
+
}
|
|
1574
|
+
function SingleChoiceList(props) {
|
|
1339
1575
|
const { binding, inputId, onAdvance } = props;
|
|
1340
1576
|
const pick = useDeliberatePick(onAdvance);
|
|
1341
|
-
const options =
|
|
1577
|
+
const options = useChoiceOptions(props.engine, binding.field.id);
|
|
1342
1578
|
const other = otherOption(binding.field);
|
|
1343
1579
|
const current = typeof binding.value === "string" ? binding.value : "";
|
|
1344
|
-
const typed = other && current && !
|
|
1580
|
+
const typed = other && current && !choiceOptions(binding.field).some((o) => o.value === current) ? current : "";
|
|
1345
1581
|
const otherText = useOtherText(typed);
|
|
1346
1582
|
const look = useChoiceLook(inputId, options.length + (other ? 1 : 0));
|
|
1347
1583
|
const textId = `${inputId}-other-text`;
|
|
@@ -1376,7 +1612,7 @@ function SingleChoiceField(props) {
|
|
|
1376
1612
|
className: look.control
|
|
1377
1613
|
}
|
|
1378
1614
|
),
|
|
1379
|
-
/* @__PURE__ */ jsx(ChoiceContent, { look, index: i, label: o.label ?? o.value })
|
|
1615
|
+
/* @__PURE__ */ jsx(ChoiceContent, { look, index: i, label: o.label ?? o.value, description: o.description, image: o.image })
|
|
1380
1616
|
] }, o.value)),
|
|
1381
1617
|
other ? (
|
|
1382
1618
|
// No auto-advance: the respondent still has to type.
|
|
@@ -1404,11 +1640,14 @@ function SingleChoiceField(props) {
|
|
|
1404
1640
|
] });
|
|
1405
1641
|
}
|
|
1406
1642
|
function MultiChoiceField(props) {
|
|
1643
|
+
return useSearchable(props) ? /* @__PURE__ */ jsx(SearchableMultiField, { ...props }) : /* @__PURE__ */ jsx(MultiChoiceList, { ...props });
|
|
1644
|
+
}
|
|
1645
|
+
function MultiChoiceList(props) {
|
|
1407
1646
|
const { binding, inputId } = props;
|
|
1408
1647
|
const selected = Array.isArray(binding.value) ? binding.value : [];
|
|
1409
|
-
const options =
|
|
1648
|
+
const options = useChoiceOptions(props.engine, binding.field.id);
|
|
1410
1649
|
const other = otherOption(binding.field);
|
|
1411
|
-
const known = new Set(
|
|
1650
|
+
const known = new Set(choiceOptions(binding.field).map((o) => o.value));
|
|
1412
1651
|
const chosen = selected.filter((v) => known.has(v));
|
|
1413
1652
|
const otherText = useOtherText(other ? selected.find((v) => !known.has(v)) ?? "" : "");
|
|
1414
1653
|
const look = useChoiceLook(inputId, options.length + (other ? 1 : 0));
|
|
@@ -1431,7 +1670,7 @@ function MultiChoiceField(props) {
|
|
|
1431
1670
|
className: look.control
|
|
1432
1671
|
}
|
|
1433
1672
|
),
|
|
1434
|
-
/* @__PURE__ */ jsx(ChoiceContent, { look, index: i, label: o.label ?? o.value })
|
|
1673
|
+
/* @__PURE__ */ jsx(ChoiceContent, { look, index: i, label: o.label ?? o.value, description: o.description, image: o.image })
|
|
1435
1674
|
] }, o.value)),
|
|
1436
1675
|
other ? /* @__PURE__ */ jsxs(Label, { htmlFor: `${inputId}-${options.length}`, className: look.row, children: [
|
|
1437
1676
|
/* @__PURE__ */ jsx(
|
|
@@ -1482,12 +1721,15 @@ function controlAria(props) {
|
|
|
1482
1721
|
};
|
|
1483
1722
|
}
|
|
1484
1723
|
function DropdownField(props) {
|
|
1724
|
+
return useSearchable(props) ? /* @__PURE__ */ jsx(SearchableSingleField, { ...props }) : /* @__PURE__ */ jsx(DropdownSelect, { ...props });
|
|
1725
|
+
}
|
|
1726
|
+
function DropdownSelect(props) {
|
|
1485
1727
|
const { binding } = props;
|
|
1486
|
-
const options =
|
|
1728
|
+
const options = useChoiceOptions(props.engine, binding.field.id);
|
|
1487
1729
|
const other = otherOption(binding.field);
|
|
1488
1730
|
const line = useLineClass();
|
|
1489
1731
|
const current = typeof binding.value === "string" ? binding.value : "";
|
|
1490
|
-
const typed = other && current && !
|
|
1732
|
+
const typed = other && current && !choiceOptions(binding.field).some((o) => o.value === current) ? current : "";
|
|
1491
1733
|
const otherText = useOtherText(typed);
|
|
1492
1734
|
const value = otherText.on ? OTHER_VALUE : current;
|
|
1493
1735
|
const textId = `${props.inputId}-other-text`;
|
|
@@ -1899,8 +2141,9 @@ function DefaultNavigation({
|
|
|
1899
2141
|
/* @__PURE__ */ jsx(Button, { type: "submit", size: "lg", loading: busy, className: "motion-reduce:[&_.animate-spin]:animate-none", children: label })
|
|
1900
2142
|
] });
|
|
1901
2143
|
}
|
|
1902
|
-
function DefaultEnding({ title, description, className }) {
|
|
2144
|
+
function DefaultEnding({ title, description, actions, className }) {
|
|
1903
2145
|
const { presentation } = useFormPresentation();
|
|
2146
|
+
const actionRow = actions ? /* @__PURE__ */ jsx("div", { className: "mt-4 flex flex-wrap justify-center gap-2", children: actions }) : null;
|
|
1904
2147
|
if (presentation === "page") {
|
|
1905
2148
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center gap-4 text-center", className), children: [
|
|
1906
2149
|
/* @__PURE__ */ jsx(
|
|
@@ -1923,12 +2166,14 @@ function DefaultEnding({ title, description, className }) {
|
|
|
1923
2166
|
children: title
|
|
1924
2167
|
}
|
|
1925
2168
|
),
|
|
1926
|
-
description ? /* @__PURE__ */ jsx("p", { className: "max-w-md text-lg text-muted-foreground text-pretty", children: description }) : null
|
|
2169
|
+
description ? /* @__PURE__ */ jsx("p", { className: "max-w-md text-lg text-muted-foreground text-pretty", children: description }) : null,
|
|
2170
|
+
actionRow
|
|
1927
2171
|
] });
|
|
1928
2172
|
}
|
|
1929
2173
|
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2 py-8 text-center", className), children: [
|
|
1930
2174
|
/* @__PURE__ */ jsx("h2", { tabIndex: -1, "data-gf-focus": "", className: cn("rounded-sm text-2xl font-semibold", focusRing), children: title }),
|
|
1931
|
-
description ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: description }) : null
|
|
2175
|
+
description ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: description }) : null,
|
|
2176
|
+
actionRow
|
|
1932
2177
|
] });
|
|
1933
2178
|
}
|
|
1934
2179
|
function focusErrorTarget(event) {
|
|
@@ -1980,7 +2225,12 @@ function Form(props) {
|
|
|
1980
2225
|
return props.engine ? /* @__PURE__ */ jsx(FormWithEngine, { ...props, engine: props.engine }) : /* @__PURE__ */ jsx(OwnedForm, { ...props });
|
|
1981
2226
|
}
|
|
1982
2227
|
function OwnedForm(props) {
|
|
1983
|
-
const
|
|
2228
|
+
const options = props.hostOptions || props.linkParams ? {
|
|
2229
|
+
...props.options,
|
|
2230
|
+
...props.hostOptions ? { hostOptions: props.hostOptions } : {},
|
|
2231
|
+
...props.linkParams ? { linkParams: props.linkParams } : {}
|
|
2232
|
+
} : props.options;
|
|
2233
|
+
const engine = useForm(props.form, options);
|
|
1984
2234
|
return /* @__PURE__ */ jsx(FormWithEngine, { ...props, engine });
|
|
1985
2235
|
}
|
|
1986
2236
|
function FormWithEngine(props) {
|
|
@@ -2022,7 +2272,7 @@ function FormBody({
|
|
|
2022
2272
|
const layout = layoutProp ?? def.layout?.mode ?? "classic";
|
|
2023
2273
|
const status = useFormSelector(engine, (s) => s.status);
|
|
2024
2274
|
const current = useFormSelector(engine, (s) => s.current);
|
|
2025
|
-
const pathKey = useFormSelector(engine, (s) => JSON.stringify(s
|
|
2275
|
+
const pathKey = useFormSelector(engine, (s) => JSON.stringify(respondentPath(def, s)));
|
|
2026
2276
|
const errorsKey = useFormSelector(engine, (s) => JSON.stringify(s.errors));
|
|
2027
2277
|
const ending = useFormSelector(engine, (s) => s.ending);
|
|
2028
2278
|
const historyLength = useFormSelector(engine, (s) => s.history.length);
|
|
@@ -2152,7 +2402,10 @@ function FormBody({
|
|
|
2152
2402
|
sending.current = false;
|
|
2153
2403
|
}
|
|
2154
2404
|
};
|
|
2155
|
-
const isLastStep = (state) =>
|
|
2405
|
+
const isLastStep = (state) => {
|
|
2406
|
+
const shown = respondentPath(def, state);
|
|
2407
|
+
return state.current === shown[shown.length - 1];
|
|
2408
|
+
};
|
|
2156
2409
|
const advance = () => {
|
|
2157
2410
|
if (waitingForUploads()) return;
|
|
2158
2411
|
direction.current = "forward";
|
|
@@ -2405,6 +2658,75 @@ function FormBody({
|
|
|
2405
2658
|
}
|
|
2406
2659
|
var classicSection = "rounded-xl border bg-card px-5 py-6 text-card-foreground sm:px-8 has-[[aria-invalid=true]]:border-destructive";
|
|
2407
2660
|
|
|
2408
|
-
|
|
2661
|
+
// src/signed-upload.ts
|
|
2662
|
+
async function putToUrl(file, upload, contentType, fetcher) {
|
|
2663
|
+
if (!upload.url) throw new Error("The server signed no URL to send the file to; pass `send`.");
|
|
2664
|
+
const res = await fetcher(upload.url, { method: "PUT", headers: { "content-type": contentType }, body: file });
|
|
2665
|
+
if (!res.ok) throw new Error(`The file wasn't stored (${res.status}).`);
|
|
2666
|
+
}
|
|
2667
|
+
function signedUploadHost(options) {
|
|
2668
|
+
const fetcher = (...args) => (options.fetch ?? fetch)(...args);
|
|
2669
|
+
return {
|
|
2670
|
+
async upload(file, field) {
|
|
2671
|
+
const type = photoFileType(file);
|
|
2672
|
+
const claim = typeof options.claim === "function" ? options.claim() : options.claim;
|
|
2673
|
+
const endpoint = typeof options.endpoint === "function" ? options.endpoint(field) : options.endpoint;
|
|
2674
|
+
const request = { fieldId: field.id, name: file.name, type, size: file.size, ...claim };
|
|
2675
|
+
const response = await fetcher(endpoint, {
|
|
2676
|
+
method: "POST",
|
|
2677
|
+
headers: { "content-type": "application/json" },
|
|
2678
|
+
body: JSON.stringify(request)
|
|
2679
|
+
});
|
|
2680
|
+
const body = await response.json().catch(() => ({}));
|
|
2681
|
+
if (!response.ok || !body.ref || !body.upload) {
|
|
2682
|
+
throw new Error(typeof body.error === "string" ? body.error : `The photo wasn't accepted (${response.status}).`);
|
|
2683
|
+
}
|
|
2684
|
+
if (options.send) await options.send(file, body.upload, type);
|
|
2685
|
+
else await putToUrl(file, body.upload, type, fetcher);
|
|
2686
|
+
return body.ref;
|
|
2687
|
+
},
|
|
2688
|
+
async url(ref, field) {
|
|
2689
|
+
if (!options.readUrl) return null;
|
|
2690
|
+
try {
|
|
2691
|
+
return await options.readUrl(ref, field);
|
|
2692
|
+
} catch {
|
|
2693
|
+
return null;
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
// src/clean-url.ts
|
|
2700
|
+
var KEY_PREFIX = "form-query:";
|
|
2701
|
+
var HIDDEN_QUERY_KEEP_MS = 30 * 60 * 1e3;
|
|
2702
|
+
function hideQueryOnLoad(win, isReload, now = Date.now()) {
|
|
2703
|
+
const { pathname, search, hash } = win.location;
|
|
2704
|
+
const key = KEY_PREFIX + pathname;
|
|
2705
|
+
try {
|
|
2706
|
+
if (search && search !== "?") {
|
|
2707
|
+
win.sessionStorage.setItem(key, JSON.stringify({ search, at: now }));
|
|
2708
|
+
const replace = nativeReplaceState(win.history);
|
|
2709
|
+
replace.call(win.history, win.history.state, "", pathname + hash);
|
|
2710
|
+
return "hidden";
|
|
2711
|
+
}
|
|
2712
|
+
const saved = win.sessionStorage.getItem(key);
|
|
2713
|
+
if (!saved) return "none";
|
|
2714
|
+
const { search: kept, at } = JSON.parse(saved);
|
|
2715
|
+
if (isReload && typeof kept === "string" && typeof at === "number" && now - at < HIDDEN_QUERY_KEEP_MS) {
|
|
2716
|
+
win.location.replace(pathname + kept + hash);
|
|
2717
|
+
return "restoring";
|
|
2718
|
+
}
|
|
2719
|
+
win.sessionStorage.removeItem(key);
|
|
2720
|
+
} catch {
|
|
2721
|
+
}
|
|
2722
|
+
return "none";
|
|
2723
|
+
}
|
|
2724
|
+
function nativeReplaceState(history) {
|
|
2725
|
+
const proto = Object.getPrototypeOf(history);
|
|
2726
|
+
return typeof proto?.replaceState === "function" ? proto.replaceState : history.replaceState;
|
|
2727
|
+
}
|
|
2728
|
+
var wasReload = () => typeof performance !== "undefined" && performance.getEntriesByType?.("navigation")[0]?.type === "reload";
|
|
2729
|
+
|
|
2730
|
+
export { CLASSIC_PROGRESS_DELAY_MS, DefaultEnding, FieldChrome, Form, HIDDEN_QUERY_KEEP_MS, MediaTimeContext, PROGRESS_DELAY_MS, SourceTextContext, UPLOAD_WAIT_MESSAGE, UploadContext, builtInRenderers, defaultRegistry, describedBy, hideQueryOnLoad, photoAccept, photoFileProblem, photoFileType, signedUploadHost, themeColors, themeCss, useFormPresentation, useMediaTime, useSourceText, useUpload, wasReload };
|
|
2409
2731
|
//# sourceMappingURL=index.js.map
|
|
2410
2732
|
//# sourceMappingURL=index.js.map
|