@petrarca/sonnet-forms 0.7.0 → 0.9.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 +4 -4
- package/dist/index.d.ts +352 -20
- package/dist/index.js +2762 -413
- package/dist/index.js.map +1 -1
- package/dist/widget-options.d.ts +2 -1
- package/dist/widget-options.js +74 -1
- package/dist/widget-options.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -446,6 +446,13 @@ import {
|
|
|
446
446
|
import { Popover, PopoverContent, PopoverTrigger } from "@petrarca/sonnet-ui";
|
|
447
447
|
|
|
448
448
|
// src/formSelectUtils.ts
|
|
449
|
+
import { extractEnumValues } from "@petrarca/sonnet-core/schema";
|
|
450
|
+
function buildSelectOptions(propertySchema, enumNames) {
|
|
451
|
+
return extractEnumValues(propertySchema).map((val) => {
|
|
452
|
+
const key = String(val);
|
|
453
|
+
return { value: key, label: enumNames[key] || key };
|
|
454
|
+
});
|
|
455
|
+
}
|
|
449
456
|
function canCreateFromSearch(allowCreate, searchQuery, options) {
|
|
450
457
|
const trimmed = searchQuery.trim();
|
|
451
458
|
if (!allowCreate || trimmed.length === 0) return false;
|
|
@@ -503,7 +510,7 @@ function SelectDropdown({
|
|
|
503
510
|
searchQuery,
|
|
504
511
|
onSearchChange,
|
|
505
512
|
loading,
|
|
506
|
-
emptyMessage,
|
|
513
|
+
emptyMessage: emptyMessage2,
|
|
507
514
|
options,
|
|
508
515
|
value,
|
|
509
516
|
onSelect,
|
|
@@ -521,7 +528,7 @@ function SelectDropdown({
|
|
|
521
528
|
}
|
|
522
529
|
),
|
|
523
530
|
/* @__PURE__ */ jsxs5(CommandList, { children: [
|
|
524
|
-
!showCreate && /* @__PURE__ */ jsx6(CommandEmpty, { children: loading ? "Loading..." :
|
|
531
|
+
!showCreate && /* @__PURE__ */ jsx6(CommandEmpty, { children: loading ? "Loading..." : emptyMessage2 }),
|
|
525
532
|
/* @__PURE__ */ jsxs5(CommandGroup, { children: [
|
|
526
533
|
options.filter(
|
|
527
534
|
(option) => option.label.toLowerCase().includes(searchQuery.toLowerCase())
|
|
@@ -596,7 +603,7 @@ function FormSelect(props) {
|
|
|
596
603
|
const {
|
|
597
604
|
placeholder,
|
|
598
605
|
searchPlaceholder,
|
|
599
|
-
emptyMessage,
|
|
606
|
+
emptyMessage: emptyMessage2,
|
|
600
607
|
clearable,
|
|
601
608
|
disabled,
|
|
602
609
|
readOnly,
|
|
@@ -675,7 +682,7 @@ function FormSelect(props) {
|
|
|
675
682
|
searchQuery,
|
|
676
683
|
onSearchChange: setSearchQuery,
|
|
677
684
|
loading,
|
|
678
|
-
emptyMessage,
|
|
685
|
+
emptyMessage: emptyMessage2,
|
|
679
686
|
options,
|
|
680
687
|
value,
|
|
681
688
|
onSelect: handleSelect,
|
|
@@ -801,7 +808,7 @@ function MultiSelectDropdown({
|
|
|
801
808
|
searchQuery,
|
|
802
809
|
onSearchChange,
|
|
803
810
|
loading,
|
|
804
|
-
emptyMessage,
|
|
811
|
+
emptyMessage: emptyMessage2,
|
|
805
812
|
options,
|
|
806
813
|
value,
|
|
807
814
|
onSelect
|
|
@@ -816,7 +823,7 @@ function MultiSelectDropdown({
|
|
|
816
823
|
}
|
|
817
824
|
),
|
|
818
825
|
/* @__PURE__ */ jsxs6(CommandList2, { children: [
|
|
819
|
-
/* @__PURE__ */ jsx7(CommandEmpty2, { children: loading ? "Loading..." :
|
|
826
|
+
/* @__PURE__ */ jsx7(CommandEmpty2, { children: loading ? "Loading..." : emptyMessage2 }),
|
|
820
827
|
/* @__PURE__ */ jsx7(CommandGroup2, { children: options.filter(
|
|
821
828
|
(option) => option.label.toLowerCase().includes(searchQuery.toLowerCase())
|
|
822
829
|
).map((option) => /* @__PURE__ */ jsxs6(
|
|
@@ -930,7 +937,7 @@ function FormMultiSelect(props) {
|
|
|
930
937
|
const {
|
|
931
938
|
placeholder,
|
|
932
939
|
searchPlaceholder,
|
|
933
|
-
emptyMessage,
|
|
940
|
+
emptyMessage: emptyMessage2,
|
|
934
941
|
clearable,
|
|
935
942
|
disabled,
|
|
936
943
|
readOnly,
|
|
@@ -1016,7 +1023,7 @@ function FormMultiSelect(props) {
|
|
|
1016
1023
|
searchQuery,
|
|
1017
1024
|
onSearchChange: setSearchQuery,
|
|
1018
1025
|
loading,
|
|
1019
|
-
emptyMessage,
|
|
1026
|
+
emptyMessage: emptyMessage2,
|
|
1020
1027
|
options,
|
|
1021
1028
|
value,
|
|
1022
1029
|
onSelect: handleSelect
|
|
@@ -1033,18 +1040,38 @@ function FormMultiSelect(props) {
|
|
|
1033
1040
|
|
|
1034
1041
|
// src/FormTagsInput.tsx
|
|
1035
1042
|
import * as React7 from "react";
|
|
1036
|
-
import { X as X3 } from "lucide-react";
|
|
1037
1043
|
import { cn as cn8 } from "@petrarca/sonnet-core";
|
|
1038
1044
|
import { Input as Input3 } from "@petrarca/sonnet-ui";
|
|
1045
|
+
|
|
1046
|
+
// src/TagBadge.tsx
|
|
1047
|
+
import { X as X3 } from "lucide-react";
|
|
1039
1048
|
import { Badge as Badge2 } from "@petrarca/sonnet-ui";
|
|
1040
1049
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1041
|
-
function TagBadge({
|
|
1050
|
+
function TagBadge({
|
|
1051
|
+
tag,
|
|
1052
|
+
index,
|
|
1053
|
+
isDisabled,
|
|
1054
|
+
onRemove,
|
|
1055
|
+
onSelect
|
|
1056
|
+
}) {
|
|
1042
1057
|
return /* @__PURE__ */ jsxs7(Badge2, { variant: "secondary", className: "gap-1", children: [
|
|
1043
|
-
|
|
1058
|
+
onSelect && !isDisabled ? /* @__PURE__ */ jsx8(
|
|
1059
|
+
"button",
|
|
1060
|
+
{
|
|
1061
|
+
type: "button",
|
|
1062
|
+
className: "outline-none hover:underline focus:underline",
|
|
1063
|
+
onClick: (e) => {
|
|
1064
|
+
e.stopPropagation();
|
|
1065
|
+
onSelect(index);
|
|
1066
|
+
},
|
|
1067
|
+
children: tag
|
|
1068
|
+
}
|
|
1069
|
+
) : tag,
|
|
1044
1070
|
!isDisabled && /* @__PURE__ */ jsx8(
|
|
1045
1071
|
"button",
|
|
1046
1072
|
{
|
|
1047
1073
|
type: "button",
|
|
1074
|
+
"aria-label": `Remove ${tag}`,
|
|
1048
1075
|
className: "ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
1049
1076
|
onKeyDown: (e) => {
|
|
1050
1077
|
if (e.key === "Enter") {
|
|
@@ -1065,6 +1092,9 @@ function TagBadge({ tag, index, isDisabled, onRemove }) {
|
|
|
1065
1092
|
)
|
|
1066
1093
|
] });
|
|
1067
1094
|
}
|
|
1095
|
+
|
|
1096
|
+
// src/FormTagsInput.tsx
|
|
1097
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1068
1098
|
function useTagManagement(value, onChange, allowDuplicates, maxTags, splitKeys) {
|
|
1069
1099
|
const [inputValue, setInputValue] = React7.useState("");
|
|
1070
1100
|
const addTag = React7.useCallback(
|
|
@@ -1146,7 +1176,7 @@ var FormTagsInput = React7.forwardRef((props, ref) => {
|
|
|
1146
1176
|
const handleContainerClick = () => {
|
|
1147
1177
|
inputRef.current?.focus();
|
|
1148
1178
|
};
|
|
1149
|
-
return /* @__PURE__ */
|
|
1179
|
+
return /* @__PURE__ */ jsx9(
|
|
1150
1180
|
FormFieldWrapper,
|
|
1151
1181
|
{
|
|
1152
1182
|
inputId: fieldId,
|
|
@@ -1156,7 +1186,7 @@ var FormTagsInput = React7.forwardRef((props, ref) => {
|
|
|
1156
1186
|
required,
|
|
1157
1187
|
labelPlacement,
|
|
1158
1188
|
className: wrapperClassName,
|
|
1159
|
-
children: /* @__PURE__ */
|
|
1189
|
+
children: /* @__PURE__ */ jsxs8(
|
|
1160
1190
|
"div",
|
|
1161
1191
|
{
|
|
1162
1192
|
className: cn8(
|
|
@@ -1166,7 +1196,7 @@ var FormTagsInput = React7.forwardRef((props, ref) => {
|
|
|
1166
1196
|
),
|
|
1167
1197
|
onClick: handleContainerClick,
|
|
1168
1198
|
children: [
|
|
1169
|
-
value.map((tag, index) => /* @__PURE__ */
|
|
1199
|
+
value.map((tag, index) => /* @__PURE__ */ jsx9(
|
|
1170
1200
|
TagBadge,
|
|
1171
1201
|
{
|
|
1172
1202
|
tag,
|
|
@@ -1176,7 +1206,7 @@ var FormTagsInput = React7.forwardRef((props, ref) => {
|
|
|
1176
1206
|
},
|
|
1177
1207
|
index
|
|
1178
1208
|
)),
|
|
1179
|
-
/* @__PURE__ */
|
|
1209
|
+
/* @__PURE__ */ jsx9(
|
|
1180
1210
|
Input3,
|
|
1181
1211
|
{
|
|
1182
1212
|
ref: inputRef,
|
|
@@ -1198,16 +1228,415 @@ var FormTagsInput = React7.forwardRef((props, ref) => {
|
|
|
1198
1228
|
});
|
|
1199
1229
|
FormTagsInput.displayName = "FormTagsInput";
|
|
1200
1230
|
|
|
1231
|
+
// src/FormKeyValueInput.tsx
|
|
1232
|
+
import * as React8 from "react";
|
|
1233
|
+
import { Plus as Plus2, X as X4 } from "lucide-react";
|
|
1234
|
+
import { cn as cn9 } from "@petrarca/sonnet-core";
|
|
1235
|
+
import { Badge as Badge3, Button as Button3, IconButton, Input as Input4 } from "@petrarca/sonnet-ui";
|
|
1236
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1237
|
+
function usePairEditor(value, onChange) {
|
|
1238
|
+
const [key, setKey] = React8.useState("");
|
|
1239
|
+
const [val, setVal] = React8.useState("");
|
|
1240
|
+
const [problem, setProblem] = React8.useState(null);
|
|
1241
|
+
const add = () => {
|
|
1242
|
+
const k = key.trim();
|
|
1243
|
+
const v = val.trim();
|
|
1244
|
+
if (!k || !v) {
|
|
1245
|
+
setProblem(!k ? "Enter a key." : "Enter a value.");
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
onChange?.({ ...value, [k]: v });
|
|
1249
|
+
setKey("");
|
|
1250
|
+
setVal("");
|
|
1251
|
+
setProblem(null);
|
|
1252
|
+
};
|
|
1253
|
+
const remove = (k) => {
|
|
1254
|
+
const { [k]: _removed, ...rest } = value;
|
|
1255
|
+
onChange?.(rest);
|
|
1256
|
+
};
|
|
1257
|
+
const replace = (from, k, v) => {
|
|
1258
|
+
const nextKey = k.trim();
|
|
1259
|
+
if (!nextKey) {
|
|
1260
|
+
setProblem("Enter a key.");
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
if (nextKey !== from && nextKey in value) {
|
|
1264
|
+
setProblem(`The key '${nextKey}' is taken.`);
|
|
1265
|
+
return false;
|
|
1266
|
+
}
|
|
1267
|
+
setProblem(null);
|
|
1268
|
+
if (nextKey === from && value[from] === v) return true;
|
|
1269
|
+
onChange?.(
|
|
1270
|
+
Object.fromEntries(
|
|
1271
|
+
Object.entries(value).map(
|
|
1272
|
+
([key0, v0]) => key0 === from ? [nextKey, v] : [key0, v0]
|
|
1273
|
+
)
|
|
1274
|
+
)
|
|
1275
|
+
);
|
|
1276
|
+
return true;
|
|
1277
|
+
};
|
|
1278
|
+
const edit = (k) => {
|
|
1279
|
+
setKey(k);
|
|
1280
|
+
setVal(value[k] ?? "");
|
|
1281
|
+
setProblem(null);
|
|
1282
|
+
};
|
|
1283
|
+
const onKeyDown = (e) => {
|
|
1284
|
+
if (e.key !== "Enter") return;
|
|
1285
|
+
e.preventDefault();
|
|
1286
|
+
add();
|
|
1287
|
+
};
|
|
1288
|
+
const typeKey = (k) => {
|
|
1289
|
+
setKey(k);
|
|
1290
|
+
setProblem(null);
|
|
1291
|
+
};
|
|
1292
|
+
const typeValue = (v) => {
|
|
1293
|
+
setVal(v);
|
|
1294
|
+
setProblem(null);
|
|
1295
|
+
};
|
|
1296
|
+
return {
|
|
1297
|
+
key,
|
|
1298
|
+
typeKey,
|
|
1299
|
+
val,
|
|
1300
|
+
typeValue,
|
|
1301
|
+
problem,
|
|
1302
|
+
add,
|
|
1303
|
+
remove,
|
|
1304
|
+
replace,
|
|
1305
|
+
edit,
|
|
1306
|
+
onKeyDown
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
function PairTags({
|
|
1310
|
+
value,
|
|
1311
|
+
separator,
|
|
1312
|
+
isDisabled,
|
|
1313
|
+
editor
|
|
1314
|
+
}) {
|
|
1315
|
+
const keys = Object.keys(value);
|
|
1316
|
+
if (keys.length === 0) return null;
|
|
1317
|
+
return /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-1.5", children: keys.map((k, index) => /* @__PURE__ */ jsx10(
|
|
1318
|
+
TagBadge,
|
|
1319
|
+
{
|
|
1320
|
+
tag: `${k}${separator}${value[k]}`,
|
|
1321
|
+
index,
|
|
1322
|
+
isDisabled,
|
|
1323
|
+
onRemove: (i) => editor.remove(keys[i]),
|
|
1324
|
+
onSelect: (i) => editor.edit(keys[i])
|
|
1325
|
+
},
|
|
1326
|
+
k
|
|
1327
|
+
)) });
|
|
1328
|
+
}
|
|
1329
|
+
function ValueInput({
|
|
1330
|
+
ariaLabel,
|
|
1331
|
+
value,
|
|
1332
|
+
onType,
|
|
1333
|
+
onPick,
|
|
1334
|
+
onBlur,
|
|
1335
|
+
onKeyDown,
|
|
1336
|
+
placeholder,
|
|
1337
|
+
readOnly,
|
|
1338
|
+
suggestions,
|
|
1339
|
+
className
|
|
1340
|
+
}) {
|
|
1341
|
+
if (!suggestions?.length)
|
|
1342
|
+
return /* @__PURE__ */ jsx10(
|
|
1343
|
+
Input4,
|
|
1344
|
+
{
|
|
1345
|
+
"aria-label": ariaLabel,
|
|
1346
|
+
value,
|
|
1347
|
+
onChange: (e) => onType(e.target.value),
|
|
1348
|
+
onBlur,
|
|
1349
|
+
onKeyDown,
|
|
1350
|
+
placeholder,
|
|
1351
|
+
readOnly,
|
|
1352
|
+
className
|
|
1353
|
+
}
|
|
1354
|
+
);
|
|
1355
|
+
const options = suggestions.map((s) => ({
|
|
1356
|
+
value: s.value,
|
|
1357
|
+
label: s.label ?? s.value
|
|
1358
|
+
}));
|
|
1359
|
+
if (value && !options.some((o) => o.value === value))
|
|
1360
|
+
options.unshift({ value, label: value });
|
|
1361
|
+
return /* @__PURE__ */ jsx10(
|
|
1362
|
+
FormSelect,
|
|
1363
|
+
{
|
|
1364
|
+
label: ariaLabel,
|
|
1365
|
+
compact: true,
|
|
1366
|
+
options,
|
|
1367
|
+
value: value || null,
|
|
1368
|
+
onChange: (picked) => onPick(picked ?? ""),
|
|
1369
|
+
allowCreate: true,
|
|
1370
|
+
onCreate: onPick,
|
|
1371
|
+
placeholder,
|
|
1372
|
+
searchPlaceholder: "Search, or type a value",
|
|
1373
|
+
readOnly,
|
|
1374
|
+
wrapperClassName: className
|
|
1375
|
+
}
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
function PairRow({
|
|
1379
|
+
pairKey,
|
|
1380
|
+
pairValue,
|
|
1381
|
+
isDisabled,
|
|
1382
|
+
editor,
|
|
1383
|
+
suggestions
|
|
1384
|
+
}) {
|
|
1385
|
+
const [key, setKey] = React8.useState(pairKey);
|
|
1386
|
+
const [val, setVal] = React8.useState(pairValue);
|
|
1387
|
+
React8.useEffect(() => setKey(pairKey), [pairKey]);
|
|
1388
|
+
React8.useEffect(() => setVal(pairValue), [pairValue]);
|
|
1389
|
+
const commit = () => {
|
|
1390
|
+
if (!editor.replace(pairKey, key, val)) setKey(pairKey);
|
|
1391
|
+
};
|
|
1392
|
+
const onKeyDown = (e) => {
|
|
1393
|
+
if (e.key !== "Enter") return;
|
|
1394
|
+
e.preventDefault();
|
|
1395
|
+
commit();
|
|
1396
|
+
};
|
|
1397
|
+
return /* @__PURE__ */ jsxs9("li", { className: "flex items-center gap-2", children: [
|
|
1398
|
+
/* @__PURE__ */ jsx10(
|
|
1399
|
+
Input4,
|
|
1400
|
+
{
|
|
1401
|
+
"aria-label": `Key of ${pairKey}`,
|
|
1402
|
+
value: key,
|
|
1403
|
+
onChange: (e) => setKey(e.target.value),
|
|
1404
|
+
onBlur: commit,
|
|
1405
|
+
onKeyDown,
|
|
1406
|
+
readOnly: isDisabled,
|
|
1407
|
+
className: "h-8 w-1/3 shrink-0 font-mono text-xs"
|
|
1408
|
+
}
|
|
1409
|
+
),
|
|
1410
|
+
/* @__PURE__ */ jsx10(
|
|
1411
|
+
ValueInput,
|
|
1412
|
+
{
|
|
1413
|
+
ariaLabel: `Value of ${pairKey}`,
|
|
1414
|
+
value: val,
|
|
1415
|
+
onType: setVal,
|
|
1416
|
+
onPick: (picked) => {
|
|
1417
|
+
setVal(picked);
|
|
1418
|
+
if (!editor.replace(pairKey, key, picked)) setVal(pairValue);
|
|
1419
|
+
},
|
|
1420
|
+
onBlur: commit,
|
|
1421
|
+
onKeyDown,
|
|
1422
|
+
readOnly: isDisabled,
|
|
1423
|
+
suggestions,
|
|
1424
|
+
className: "h-8 min-w-0 flex-1 font-mono text-xs"
|
|
1425
|
+
}
|
|
1426
|
+
),
|
|
1427
|
+
!isDisabled && /* @__PURE__ */ jsx10(
|
|
1428
|
+
IconButton,
|
|
1429
|
+
{
|
|
1430
|
+
label: `Remove ${pairKey}`,
|
|
1431
|
+
tone: "destructive",
|
|
1432
|
+
onClick: () => editor.remove(pairKey),
|
|
1433
|
+
children: /* @__PURE__ */ jsx10(X4, {})
|
|
1434
|
+
}
|
|
1435
|
+
)
|
|
1436
|
+
] });
|
|
1437
|
+
}
|
|
1438
|
+
function PairRows({
|
|
1439
|
+
value,
|
|
1440
|
+
isDisabled,
|
|
1441
|
+
editor,
|
|
1442
|
+
suggestions
|
|
1443
|
+
}) {
|
|
1444
|
+
const keys = Object.keys(value);
|
|
1445
|
+
if (keys.length === 0) return null;
|
|
1446
|
+
return /* @__PURE__ */ jsx10("ul", { className: "space-y-1.5", children: keys.map((k, index) => /* @__PURE__ */ jsx10(
|
|
1447
|
+
PairRow,
|
|
1448
|
+
{
|
|
1449
|
+
pairKey: k,
|
|
1450
|
+
pairValue: value[k],
|
|
1451
|
+
isDisabled,
|
|
1452
|
+
editor,
|
|
1453
|
+
suggestions
|
|
1454
|
+
},
|
|
1455
|
+
index
|
|
1456
|
+
)) });
|
|
1457
|
+
}
|
|
1458
|
+
function PairKeys({
|
|
1459
|
+
value
|
|
1460
|
+
}) {
|
|
1461
|
+
const keys = Object.keys(value);
|
|
1462
|
+
return /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-1.5", children: keys.map((k) => /* @__PURE__ */ jsx10(
|
|
1463
|
+
Badge3,
|
|
1464
|
+
{
|
|
1465
|
+
variant: "outline",
|
|
1466
|
+
className: "font-mono text-[11px] font-normal",
|
|
1467
|
+
title: value[k],
|
|
1468
|
+
children: k
|
|
1469
|
+
},
|
|
1470
|
+
k
|
|
1471
|
+
)) });
|
|
1472
|
+
}
|
|
1473
|
+
function PairList({
|
|
1474
|
+
display,
|
|
1475
|
+
suggestions,
|
|
1476
|
+
...props
|
|
1477
|
+
}) {
|
|
1478
|
+
return display === "rows" ? /* @__PURE__ */ jsx10(PairRows, { ...props, suggestions }) : /* @__PURE__ */ jsx10(PairTags, { ...props });
|
|
1479
|
+
}
|
|
1480
|
+
var PairFields = React8.forwardRef(
|
|
1481
|
+
({
|
|
1482
|
+
id,
|
|
1483
|
+
name,
|
|
1484
|
+
keyPlaceholder = "Key",
|
|
1485
|
+
valuePlaceholder = "Value",
|
|
1486
|
+
asRow = false,
|
|
1487
|
+
editor,
|
|
1488
|
+
suggestions
|
|
1489
|
+
}, ref) => /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
|
|
1490
|
+
/* @__PURE__ */ jsx10(
|
|
1491
|
+
Input4,
|
|
1492
|
+
{
|
|
1493
|
+
ref,
|
|
1494
|
+
id,
|
|
1495
|
+
name,
|
|
1496
|
+
value: editor.key,
|
|
1497
|
+
onChange: (e) => editor.typeKey(e.target.value),
|
|
1498
|
+
onKeyDown: editor.onKeyDown,
|
|
1499
|
+
placeholder: keyPlaceholder,
|
|
1500
|
+
className: cn9("h-8", asRow && "w-1/3 shrink-0 font-mono text-xs")
|
|
1501
|
+
}
|
|
1502
|
+
),
|
|
1503
|
+
/* @__PURE__ */ jsx10(
|
|
1504
|
+
ValueInput,
|
|
1505
|
+
{
|
|
1506
|
+
ariaLabel: valuePlaceholder,
|
|
1507
|
+
value: editor.val,
|
|
1508
|
+
onType: editor.typeValue,
|
|
1509
|
+
onPick: editor.typeValue,
|
|
1510
|
+
onKeyDown: editor.onKeyDown,
|
|
1511
|
+
placeholder: valuePlaceholder,
|
|
1512
|
+
suggestions,
|
|
1513
|
+
className: cn9("h-8", asRow && "min-w-0 flex-1 font-mono text-xs")
|
|
1514
|
+
}
|
|
1515
|
+
),
|
|
1516
|
+
/* @__PURE__ */ jsx10(
|
|
1517
|
+
Button3,
|
|
1518
|
+
{
|
|
1519
|
+
type: "button",
|
|
1520
|
+
variant: "outline",
|
|
1521
|
+
size: "compact",
|
|
1522
|
+
"aria-label": "Add",
|
|
1523
|
+
onClick: editor.add,
|
|
1524
|
+
children: /* @__PURE__ */ jsx10(Plus2, { className: "h-4 w-4" })
|
|
1525
|
+
}
|
|
1526
|
+
)
|
|
1527
|
+
] })
|
|
1528
|
+
);
|
|
1529
|
+
PairFields.displayName = "PairFields";
|
|
1530
|
+
function PairBox({
|
|
1531
|
+
isDisabled,
|
|
1532
|
+
hasError,
|
|
1533
|
+
children
|
|
1534
|
+
}) {
|
|
1535
|
+
return /* @__PURE__ */ jsx10(
|
|
1536
|
+
"div",
|
|
1537
|
+
{
|
|
1538
|
+
className: cn9(
|
|
1539
|
+
"w-full space-y-2 rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
1540
|
+
isDisabled && "cursor-not-allowed opacity-50",
|
|
1541
|
+
hasError && "border-destructive"
|
|
1542
|
+
),
|
|
1543
|
+
children
|
|
1544
|
+
}
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
var PairEditorBox = React8.forwardRef(
|
|
1548
|
+
({
|
|
1549
|
+
display,
|
|
1550
|
+
value,
|
|
1551
|
+
separator,
|
|
1552
|
+
isDisabled,
|
|
1553
|
+
hasError,
|
|
1554
|
+
editor,
|
|
1555
|
+
suggestions,
|
|
1556
|
+
...fields
|
|
1557
|
+
}, ref) => /* @__PURE__ */ jsxs9(PairBox, { isDisabled, hasError, children: [
|
|
1558
|
+
/* @__PURE__ */ jsx10(
|
|
1559
|
+
PairList,
|
|
1560
|
+
{
|
|
1561
|
+
display,
|
|
1562
|
+
value,
|
|
1563
|
+
separator,
|
|
1564
|
+
isDisabled,
|
|
1565
|
+
editor,
|
|
1566
|
+
suggestions
|
|
1567
|
+
}
|
|
1568
|
+
),
|
|
1569
|
+
!isDisabled && /* @__PURE__ */ jsx10(
|
|
1570
|
+
PairFields,
|
|
1571
|
+
{
|
|
1572
|
+
ref,
|
|
1573
|
+
...fields,
|
|
1574
|
+
asRow: display === "rows",
|
|
1575
|
+
editor,
|
|
1576
|
+
suggestions
|
|
1577
|
+
}
|
|
1578
|
+
)
|
|
1579
|
+
] })
|
|
1580
|
+
);
|
|
1581
|
+
PairEditorBox.displayName = "PairEditorBox";
|
|
1582
|
+
var FormKeyValueInput = React8.forwardRef((props, ref) => {
|
|
1583
|
+
const {
|
|
1584
|
+
label,
|
|
1585
|
+
description,
|
|
1586
|
+
error,
|
|
1587
|
+
wrapperClassName,
|
|
1588
|
+
labelPlacement,
|
|
1589
|
+
id,
|
|
1590
|
+
value = {},
|
|
1591
|
+
onChange,
|
|
1592
|
+
separator = ":",
|
|
1593
|
+
display = "tags"
|
|
1594
|
+
} = props;
|
|
1595
|
+
const generatedId = React8.useId();
|
|
1596
|
+
const fieldId = id ?? generatedId;
|
|
1597
|
+
const editor = usePairEditor(value, onChange);
|
|
1598
|
+
return /* @__PURE__ */ jsx10(
|
|
1599
|
+
FormFieldWrapper,
|
|
1600
|
+
{
|
|
1601
|
+
inputId: fieldId,
|
|
1602
|
+
label: resolveFieldLabel(label),
|
|
1603
|
+
description,
|
|
1604
|
+
error: error ?? editor.problem ?? void 0,
|
|
1605
|
+
required: props.required ?? false,
|
|
1606
|
+
labelPlacement,
|
|
1607
|
+
className: wrapperClassName,
|
|
1608
|
+
children: display === "keys" ? /* @__PURE__ */ jsx10(PairKeys, { value }) : /* @__PURE__ */ jsx10(
|
|
1609
|
+
PairEditorBox,
|
|
1610
|
+
{
|
|
1611
|
+
ref,
|
|
1612
|
+
display,
|
|
1613
|
+
value,
|
|
1614
|
+
separator,
|
|
1615
|
+
isDisabled: Boolean(props.disabled || props.readOnly),
|
|
1616
|
+
hasError: Boolean(error),
|
|
1617
|
+
id: fieldId,
|
|
1618
|
+
name: props.name,
|
|
1619
|
+
keyPlaceholder: props.keyPlaceholder,
|
|
1620
|
+
valuePlaceholder: props.valuePlaceholder,
|
|
1621
|
+
editor,
|
|
1622
|
+
suggestions: props.valueSuggestions
|
|
1623
|
+
}
|
|
1624
|
+
)
|
|
1625
|
+
}
|
|
1626
|
+
);
|
|
1627
|
+
});
|
|
1628
|
+
FormKeyValueInput.displayName = "FormKeyValueInput";
|
|
1629
|
+
|
|
1201
1630
|
// src/FormQuantityInput.tsx
|
|
1202
|
-
import
|
|
1631
|
+
import React9, { useCallback as useCallback2, useEffect as useEffect3, useId as useId9, useMemo, useState as useState5 } from "react";
|
|
1203
1632
|
import { ChevronsUpDown as ChevronsUpDown3 } from "lucide-react";
|
|
1204
|
-
import { cn as
|
|
1633
|
+
import { cn as cn10 } from "@petrarca/sonnet-core";
|
|
1205
1634
|
import {
|
|
1206
1635
|
InputGroup,
|
|
1207
1636
|
InputGroupAddon,
|
|
1208
1637
|
InputGroupInput
|
|
1209
1638
|
} from "@petrarca/sonnet-ui";
|
|
1210
|
-
import { jsx as
|
|
1639
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1211
1640
|
var VALID_NUMERIC_INPUT = /^-?\d*[.,]?\d*$/;
|
|
1212
1641
|
var INCOMPLETE_SENTINELS = /* @__PURE__ */ new Set(["", ".", "-", "-."]);
|
|
1213
1642
|
function roundToPrecision(n, p) {
|
|
@@ -1228,7 +1657,31 @@ function roundAndFormat(numValue, precision) {
|
|
|
1228
1657
|
const formatted = precision !== void 0 ? rounded.toFixed(precision) : String(rounded);
|
|
1229
1658
|
return { rounded, formatted };
|
|
1230
1659
|
}
|
|
1231
|
-
function
|
|
1660
|
+
function FreeUnitAddon({
|
|
1661
|
+
currentUnit,
|
|
1662
|
+
addonAlign,
|
|
1663
|
+
disabled,
|
|
1664
|
+
readOnly,
|
|
1665
|
+
onChange
|
|
1666
|
+
}) {
|
|
1667
|
+
return /* @__PURE__ */ jsx11(InputGroupAddon, { align: addonAlign, children: /* @__PURE__ */ jsx11(
|
|
1668
|
+
"input",
|
|
1669
|
+
{
|
|
1670
|
+
value: currentUnit,
|
|
1671
|
+
onChange,
|
|
1672
|
+
disabled,
|
|
1673
|
+
readOnly,
|
|
1674
|
+
placeholder: "unit",
|
|
1675
|
+
"aria-label": "Unit",
|
|
1676
|
+
className: "w-16 border-0 bg-transparent px-1 text-right text-sm text-muted-foreground outline-none placeholder:text-muted-foreground/60 focus:text-foreground"
|
|
1677
|
+
}
|
|
1678
|
+
) });
|
|
1679
|
+
}
|
|
1680
|
+
function UnitAddon({ units, freeUnit, ...rest }) {
|
|
1681
|
+
if (units.length === 0) return freeUnit ? /* @__PURE__ */ jsx11(FreeUnitAddon, { ...rest }) : null;
|
|
1682
|
+
return /* @__PURE__ */ jsx11(UnitPicker, { units, ...rest });
|
|
1683
|
+
}
|
|
1684
|
+
function UnitPicker({
|
|
1232
1685
|
units,
|
|
1233
1686
|
currentUnit,
|
|
1234
1687
|
addonAlign,
|
|
@@ -1236,12 +1689,11 @@ function UnitAddon({
|
|
|
1236
1689
|
readOnly,
|
|
1237
1690
|
onChange
|
|
1238
1691
|
}) {
|
|
1239
|
-
if (units.length === 0) return null;
|
|
1240
1692
|
if (units.length === 1) {
|
|
1241
|
-
return /* @__PURE__ */
|
|
1693
|
+
return /* @__PURE__ */ jsx11(InputGroupAddon, { align: addonAlign, children: /* @__PURE__ */ jsx11("span", { className: "text-sm text-muted-foreground", children: units[0].label }) });
|
|
1242
1694
|
}
|
|
1243
|
-
return /* @__PURE__ */
|
|
1244
|
-
/* @__PURE__ */
|
|
1695
|
+
return /* @__PURE__ */ jsx11(InputGroupAddon, { align: addonAlign, children: /* @__PURE__ */ jsxs10("div", { className: "relative flex items-center", children: [
|
|
1696
|
+
/* @__PURE__ */ jsx11(
|
|
1245
1697
|
"select",
|
|
1246
1698
|
{
|
|
1247
1699
|
value: currentUnit,
|
|
@@ -1249,15 +1701,16 @@ function UnitAddon({
|
|
|
1249
1701
|
disabled: disabled || readOnly,
|
|
1250
1702
|
className: "cursor-pointer appearance-none border-0 bg-transparent py-0 pr-5 pl-1 text-sm text-muted-foreground outline-none hover:text-foreground focus:text-foreground",
|
|
1251
1703
|
"aria-label": "Unit",
|
|
1252
|
-
children: units.map((u) => /* @__PURE__ */
|
|
1704
|
+
children: units.map((u) => /* @__PURE__ */ jsx11("option", { value: u.value, children: u.label }, u.value))
|
|
1253
1705
|
}
|
|
1254
1706
|
),
|
|
1255
|
-
/* @__PURE__ */
|
|
1707
|
+
/* @__PURE__ */ jsx11(ChevronsUpDown3, { className: "pointer-events-none absolute right-0 h-3.5 w-3.5 text-muted-foreground/60" })
|
|
1256
1708
|
] }) });
|
|
1257
1709
|
}
|
|
1258
1710
|
function useUnitState(value, units, unitPosition, min, max) {
|
|
1259
1711
|
const currentUnit = useMemo(() => {
|
|
1260
1712
|
const preferred = value?.unit;
|
|
1713
|
+
if (units.length === 0) return preferred ?? "";
|
|
1261
1714
|
if (preferred && units.some((u) => u.value === preferred)) return preferred;
|
|
1262
1715
|
return units[0]?.value ?? "";
|
|
1263
1716
|
}, [value?.unit, units]);
|
|
@@ -1272,11 +1725,11 @@ function useUnitState(value, units, unitPosition, min, max) {
|
|
|
1272
1725
|
return { currentUnit, effectiveMin, effectiveMax, addonAlign };
|
|
1273
1726
|
}
|
|
1274
1727
|
function useQuantityInput(value, onChange, onBlur, currentUnit, precision, effectiveMin, effectiveMax, externalError) {
|
|
1275
|
-
const [rawInput, setRawInput] =
|
|
1728
|
+
const [rawInput, setRawInput] = useState5(
|
|
1276
1729
|
value?.value !== void 0 ? String(value.value) : ""
|
|
1277
1730
|
);
|
|
1278
|
-
const [validationError, setValidationError] =
|
|
1279
|
-
|
|
1731
|
+
const [validationError, setValidationError] = useState5("");
|
|
1732
|
+
useEffect3(() => {
|
|
1280
1733
|
const external = value?.value !== void 0 ? String(value.value) : "";
|
|
1281
1734
|
setRawInput((prev) => {
|
|
1282
1735
|
const parsed = parseFloat(prev.replace(",", "."));
|
|
@@ -1341,7 +1794,7 @@ function useQuantityInput(value, onChange, onBlur, currentUnit, precision, effec
|
|
|
1341
1794
|
setValidationError
|
|
1342
1795
|
};
|
|
1343
1796
|
}
|
|
1344
|
-
var FormQuantityInput =
|
|
1797
|
+
var FormQuantityInput = React9.forwardRef((allProps, ref) => {
|
|
1345
1798
|
const {
|
|
1346
1799
|
value,
|
|
1347
1800
|
onChange,
|
|
@@ -1360,12 +1813,13 @@ var FormQuantityInput = React8.forwardRef((allProps, ref) => {
|
|
|
1360
1813
|
wrapperClassName,
|
|
1361
1814
|
labelPlacement,
|
|
1362
1815
|
className,
|
|
1363
|
-
name
|
|
1816
|
+
name,
|
|
1817
|
+
freeUnit
|
|
1364
1818
|
} = allProps;
|
|
1365
1819
|
const units = useMemo(() => allProps.units ?? [], [allProps.units]);
|
|
1366
1820
|
const unitPosition = allProps.unitPosition ?? "suffix";
|
|
1367
1821
|
const error = allProps.error;
|
|
1368
|
-
const generatedId =
|
|
1822
|
+
const generatedId = useId9();
|
|
1369
1823
|
const inputId = allProps.id ?? generatedId;
|
|
1370
1824
|
const { currentUnit, effectiveMin, effectiveMax, addonAlign } = useUnitState(
|
|
1371
1825
|
value,
|
|
@@ -1397,7 +1851,7 @@ var FormQuantityInput = React8.forwardRef((allProps, ref) => {
|
|
|
1397
1851
|
setValidationError("");
|
|
1398
1852
|
const normalized = rawInput.replace(",", ".");
|
|
1399
1853
|
const numValue = parseFloat(normalized);
|
|
1400
|
-
if (isNaN(numValue)) return;
|
|
1854
|
+
if (isNaN(numValue)) return emit(void 0, unitValue);
|
|
1401
1855
|
const um = units.find((u) => u.value === unitValue);
|
|
1402
1856
|
const lo = um?.min ?? min;
|
|
1403
1857
|
const hi = um?.max ?? max;
|
|
@@ -1407,10 +1861,11 @@ var FormQuantityInput = React8.forwardRef((allProps, ref) => {
|
|
|
1407
1861
|
[rawInput, units, min, max, emit, setValidationError]
|
|
1408
1862
|
);
|
|
1409
1863
|
const unitAddon = useMemo(
|
|
1410
|
-
() => /* @__PURE__ */
|
|
1864
|
+
() => /* @__PURE__ */ jsx11(
|
|
1411
1865
|
UnitAddon,
|
|
1412
1866
|
{
|
|
1413
1867
|
units,
|
|
1868
|
+
freeUnit,
|
|
1414
1869
|
currentUnit,
|
|
1415
1870
|
addonAlign,
|
|
1416
1871
|
disabled,
|
|
@@ -1418,9 +1873,17 @@ var FormQuantityInput = React8.forwardRef((allProps, ref) => {
|
|
|
1418
1873
|
onChange: handleUnitChange
|
|
1419
1874
|
}
|
|
1420
1875
|
),
|
|
1421
|
-
[
|
|
1876
|
+
[
|
|
1877
|
+
units,
|
|
1878
|
+
freeUnit,
|
|
1879
|
+
currentUnit,
|
|
1880
|
+
addonAlign,
|
|
1881
|
+
disabled,
|
|
1882
|
+
readOnly,
|
|
1883
|
+
handleUnitChange
|
|
1884
|
+
]
|
|
1422
1885
|
);
|
|
1423
|
-
return /* @__PURE__ */
|
|
1886
|
+
return /* @__PURE__ */ jsx11(
|
|
1424
1887
|
FormFieldWrapper,
|
|
1425
1888
|
{
|
|
1426
1889
|
inputId,
|
|
@@ -1430,13 +1893,13 @@ var FormQuantityInput = React8.forwardRef((allProps, ref) => {
|
|
|
1430
1893
|
required,
|
|
1431
1894
|
labelPlacement,
|
|
1432
1895
|
className: wrapperClassName,
|
|
1433
|
-
children: /* @__PURE__ */
|
|
1896
|
+
children: /* @__PURE__ */ jsxs10(
|
|
1434
1897
|
InputGroup,
|
|
1435
1898
|
{
|
|
1436
1899
|
"data-disabled": disabled || void 0,
|
|
1437
|
-
className:
|
|
1900
|
+
className: cn10(resolvedError && "border-destructive"),
|
|
1438
1901
|
children: [
|
|
1439
|
-
/* @__PURE__ */
|
|
1902
|
+
/* @__PURE__ */ jsx11(
|
|
1440
1903
|
InputGroupInput,
|
|
1441
1904
|
{
|
|
1442
1905
|
id: inputId,
|
|
@@ -1474,9 +1937,13 @@ var FormQuantityInput = React8.forwardRef((allProps, ref) => {
|
|
|
1474
1937
|
FormQuantityInput.displayName = "FormQuantityInput";
|
|
1475
1938
|
|
|
1476
1939
|
// src/FormActions.tsx
|
|
1477
|
-
import { Button as
|
|
1940
|
+
import { Button as Button4 } from "@petrarca/sonnet-ui";
|
|
1478
1941
|
import { SimpleGroup } from "@petrarca/sonnet-ui";
|
|
1479
|
-
import { jsx as
|
|
1942
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1943
|
+
function isSaveDisabled(args) {
|
|
1944
|
+
if (args.disabled || !args.hasChanges || args.isValidating) return true;
|
|
1945
|
+
return args.saveWhenInvalid === "disable" && !args.isValid;
|
|
1946
|
+
}
|
|
1480
1947
|
function FormActions({
|
|
1481
1948
|
onSave,
|
|
1482
1949
|
onCancel,
|
|
@@ -1487,14 +1954,39 @@ function FormActions({
|
|
|
1487
1954
|
saveLabel = "Save",
|
|
1488
1955
|
cancelLabel = "Cancel",
|
|
1489
1956
|
showCancel = true,
|
|
1957
|
+
isValidating = false,
|
|
1490
1958
|
leadingActions
|
|
1491
1959
|
}) {
|
|
1492
|
-
const saveDisabled =
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1960
|
+
const saveDisabled = isSaveDisabled({
|
|
1961
|
+
disabled,
|
|
1962
|
+
hasChanges,
|
|
1963
|
+
isValid,
|
|
1964
|
+
isValidating,
|
|
1965
|
+
saveWhenInvalid
|
|
1966
|
+
});
|
|
1967
|
+
const keepFocus = (e) => e.preventDefault();
|
|
1968
|
+
return /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between mt-4", children: [
|
|
1969
|
+
/* @__PURE__ */ jsx12("div", { className: "flex items-center gap-2", children: leadingActions }),
|
|
1970
|
+
/* @__PURE__ */ jsxs11(SimpleGroup, { justify: "flex-end", children: [
|
|
1971
|
+
showCancel && /* @__PURE__ */ jsx12(
|
|
1972
|
+
Button4,
|
|
1973
|
+
{
|
|
1974
|
+
variant: "outline",
|
|
1975
|
+
onClick: onCancel,
|
|
1976
|
+
onMouseDown: keepFocus,
|
|
1977
|
+
disabled,
|
|
1978
|
+
children: cancelLabel
|
|
1979
|
+
}
|
|
1980
|
+
),
|
|
1981
|
+
/* @__PURE__ */ jsx12(
|
|
1982
|
+
Button4,
|
|
1983
|
+
{
|
|
1984
|
+
onClick: onSave,
|
|
1985
|
+
onMouseDown: keepFocus,
|
|
1986
|
+
disabled: saveDisabled,
|
|
1987
|
+
children: saveLabel
|
|
1988
|
+
}
|
|
1989
|
+
)
|
|
1498
1990
|
] })
|
|
1499
1991
|
] });
|
|
1500
1992
|
}
|
|
@@ -1505,7 +1997,7 @@ import { ChevronDown, ChevronRight } from "lucide-react";
|
|
|
1505
1997
|
import { Card } from "@petrarca/sonnet-ui";
|
|
1506
1998
|
import { useDisclosure } from "@petrarca/sonnet-core/hooks";
|
|
1507
1999
|
import { Collapsible, CollapsibleContent } from "@petrarca/sonnet-ui";
|
|
1508
|
-
import { jsx as
|
|
2000
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1509
2001
|
function ExtraPropertiesCard({ properties, schema }) {
|
|
1510
2002
|
const [opened, { toggle }] = useDisclosure(false);
|
|
1511
2003
|
const extraProperties = useMemo2(() => {
|
|
@@ -1517,32 +2009,35 @@ function ExtraPropertiesCard({ properties, schema }) {
|
|
|
1517
2009
|
return Object.keys(extra).length > 0 ? extra : null;
|
|
1518
2010
|
}, [properties, schema]);
|
|
1519
2011
|
if (!extraProperties) return null;
|
|
1520
|
-
return /* @__PURE__ */
|
|
1521
|
-
/* @__PURE__ */
|
|
2012
|
+
return /* @__PURE__ */ jsxs12(Card, { className: "border p-3", children: [
|
|
2013
|
+
/* @__PURE__ */ jsxs12(
|
|
1522
2014
|
"p",
|
|
1523
2015
|
{
|
|
1524
2016
|
className: `flex items-center gap-1 text-sm font-medium cursor-pointer select-none ${opened ? "mb-1" : ""}`,
|
|
1525
2017
|
onClick: toggle,
|
|
1526
2018
|
children: [
|
|
1527
|
-
opened ? /* @__PURE__ */
|
|
2019
|
+
opened ? /* @__PURE__ */ jsx13(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx13(ChevronRight, { className: "h-3.5 w-3.5" }),
|
|
1528
2020
|
"Extra Properties (not in schema)"
|
|
1529
2021
|
]
|
|
1530
2022
|
}
|
|
1531
2023
|
),
|
|
1532
|
-
/* @__PURE__ */
|
|
2024
|
+
/* @__PURE__ */ jsx13(Collapsible, { open: opened, children: /* @__PURE__ */ jsx13(CollapsibleContent, { children: /* @__PURE__ */ jsx13("pre", { className: "text-xs bg-muted p-2 rounded overflow-x-auto", children: /* @__PURE__ */ jsx13("code", { children: JSON.stringify(extraProperties, null, 2) }) }) }) })
|
|
1533
2025
|
] });
|
|
1534
2026
|
}
|
|
1535
2027
|
var ExtraPropertiesCard_default = ExtraPropertiesCard;
|
|
1536
2028
|
|
|
1537
2029
|
// src/JsonSchemaFormRenderer.tsx
|
|
1538
|
-
import
|
|
2030
|
+
import React18, { useCallback as useCallback9, useContext as useContext2, useMemo as useMemo12 } from "react";
|
|
1539
2031
|
import { SimpleStack } from "@petrarca/sonnet-ui";
|
|
1540
2032
|
import { AlertCircle as AlertCircle2 } from "lucide-react";
|
|
1541
2033
|
import {
|
|
1542
2034
|
WIDTH_TOKEN_REMS
|
|
1543
2035
|
} from "@petrarca/sonnet-core/schema";
|
|
1544
|
-
import {
|
|
1545
|
-
|
|
2036
|
+
import {
|
|
2037
|
+
isFieldRequired,
|
|
2038
|
+
evaluateUiRule,
|
|
2039
|
+
stripFormItemIds as stripFormItemIds2
|
|
2040
|
+
} from "@petrarca/sonnet-core/schema";
|
|
1546
2041
|
|
|
1547
2042
|
// src/widgets/types.ts
|
|
1548
2043
|
function firstError(rawErrors) {
|
|
@@ -1550,7 +2045,7 @@ function firstError(rawErrors) {
|
|
|
1550
2045
|
}
|
|
1551
2046
|
|
|
1552
2047
|
// src/widgets/TextInputWidget.tsx
|
|
1553
|
-
import { jsx as
|
|
2048
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1554
2049
|
function inputTypeFromFormat(format) {
|
|
1555
2050
|
if (format === "email") return "email";
|
|
1556
2051
|
if (format === "uri" || format === "url") return "url";
|
|
@@ -1575,7 +2070,7 @@ function TextInputWidget({
|
|
|
1575
2070
|
}) {
|
|
1576
2071
|
const error = firstError(rawErrors);
|
|
1577
2072
|
const compact = options?.compact;
|
|
1578
|
-
return /* @__PURE__ */
|
|
2073
|
+
return /* @__PURE__ */ jsx14(
|
|
1579
2074
|
FormInput,
|
|
1580
2075
|
{
|
|
1581
2076
|
id,
|
|
@@ -1602,7 +2097,7 @@ function TextInputWidget({
|
|
|
1602
2097
|
}
|
|
1603
2098
|
|
|
1604
2099
|
// src/widgets/TextareaWidget.tsx
|
|
1605
|
-
import { jsx as
|
|
2100
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1606
2101
|
function TextareaWidget({
|
|
1607
2102
|
id,
|
|
1608
2103
|
name,
|
|
@@ -1623,7 +2118,7 @@ function TextareaWidget({
|
|
|
1623
2118
|
const minRows = options.rows || options.minRows || 3;
|
|
1624
2119
|
const autosize = options.autosize !== false;
|
|
1625
2120
|
const error = firstError(rawErrors);
|
|
1626
|
-
return /* @__PURE__ */
|
|
2121
|
+
return /* @__PURE__ */ jsx15(
|
|
1627
2122
|
FormTextarea,
|
|
1628
2123
|
{
|
|
1629
2124
|
id,
|
|
@@ -1649,7 +2144,7 @@ function TextareaWidget({
|
|
|
1649
2144
|
}
|
|
1650
2145
|
|
|
1651
2146
|
// src/widgets/NumberWidget.tsx
|
|
1652
|
-
import { jsx as
|
|
2147
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1653
2148
|
function NumberWidget({
|
|
1654
2149
|
id,
|
|
1655
2150
|
name,
|
|
@@ -1671,7 +2166,7 @@ function NumberWidget({
|
|
|
1671
2166
|
const step = options.step ?? (isInteger ? 1 : void 0);
|
|
1672
2167
|
const allowDecimal = options.allowDecimal ?? !isInteger;
|
|
1673
2168
|
const error = firstError(rawErrors);
|
|
1674
|
-
return /* @__PURE__ */
|
|
2169
|
+
return /* @__PURE__ */ jsx16(
|
|
1675
2170
|
FormNumberInput,
|
|
1676
2171
|
{
|
|
1677
2172
|
id,
|
|
@@ -1699,7 +2194,7 @@ function NumberWidget({
|
|
|
1699
2194
|
// src/widgets/CheckboxWidget.tsx
|
|
1700
2195
|
import { useCallback as useCallback3 } from "react";
|
|
1701
2196
|
import { isNullableBoolean } from "@petrarca/sonnet-core/schema";
|
|
1702
|
-
import { jsx as
|
|
2197
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1703
2198
|
function CheckboxWidget({
|
|
1704
2199
|
id,
|
|
1705
2200
|
value,
|
|
@@ -1718,12 +2213,12 @@ function CheckboxWidget({
|
|
|
1718
2213
|
const compact = options?.compact;
|
|
1719
2214
|
const isTriState = isNullableBoolean(propertySchema);
|
|
1720
2215
|
const isChecked = value === true;
|
|
1721
|
-
const isIndeterminate = isTriState && value
|
|
2216
|
+
const isIndeterminate = isTriState && value == null;
|
|
1722
2217
|
const checkedState = isIndeterminate ? "indeterminate" : isChecked;
|
|
1723
2218
|
const handleChange = useCallback3(
|
|
1724
2219
|
(_newChecked) => {
|
|
1725
2220
|
if (isTriState) {
|
|
1726
|
-
if (value
|
|
2221
|
+
if (value == null) {
|
|
1727
2222
|
onChange(true);
|
|
1728
2223
|
} else if (value === true) {
|
|
1729
2224
|
onChange(false);
|
|
@@ -1736,7 +2231,7 @@ function CheckboxWidget({
|
|
|
1736
2231
|
},
|
|
1737
2232
|
[isTriState, value, onChange]
|
|
1738
2233
|
);
|
|
1739
|
-
return /* @__PURE__ */
|
|
2234
|
+
return /* @__PURE__ */ jsx17(
|
|
1740
2235
|
FormCheckbox,
|
|
1741
2236
|
{
|
|
1742
2237
|
id,
|
|
@@ -1756,15 +2251,7 @@ function CheckboxWidget({
|
|
|
1756
2251
|
|
|
1757
2252
|
// src/widgets/SelectWidget.tsx
|
|
1758
2253
|
import { useRef as useRef3 } from "react";
|
|
1759
|
-
import {
|
|
1760
|
-
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1761
|
-
function buildSelectOptions(propertySchema, enumNames) {
|
|
1762
|
-
const enumValues = extractEnumValues(propertySchema);
|
|
1763
|
-
return enumValues.map((val) => {
|
|
1764
|
-
const key = String(val);
|
|
1765
|
-
return { value: key, label: enumNames[key] || key };
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
2254
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1768
2255
|
function normalizeMultiValue(value) {
|
|
1769
2256
|
if (Array.isArray(value)) return value.map(String);
|
|
1770
2257
|
if (value) return [String(value)];
|
|
@@ -1804,7 +2291,7 @@ function SelectWidget(props) {
|
|
|
1804
2291
|
const triggerClassName = compact ? "h-8 text-xs" : void 0;
|
|
1805
2292
|
const handleBlur = () => onBlur?.(id, valueRef.current);
|
|
1806
2293
|
if (isMultiple) {
|
|
1807
|
-
return /* @__PURE__ */
|
|
2294
|
+
return /* @__PURE__ */ jsx18(
|
|
1808
2295
|
FormMultiSelect,
|
|
1809
2296
|
{
|
|
1810
2297
|
id,
|
|
@@ -1828,7 +2315,7 @@ function SelectWidget(props) {
|
|
|
1828
2315
|
}
|
|
1829
2316
|
);
|
|
1830
2317
|
}
|
|
1831
|
-
return /* @__PURE__ */
|
|
2318
|
+
return /* @__PURE__ */ jsx18(
|
|
1832
2319
|
FormSelect,
|
|
1833
2320
|
{
|
|
1834
2321
|
id,
|
|
@@ -1856,7 +2343,7 @@ function SelectWidget(props) {
|
|
|
1856
2343
|
}
|
|
1857
2344
|
|
|
1858
2345
|
// src/widgets/TagsInputWidget.tsx
|
|
1859
|
-
import { jsx as
|
|
2346
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1860
2347
|
function TagsInputWidget({
|
|
1861
2348
|
id,
|
|
1862
2349
|
name,
|
|
@@ -1873,7 +2360,7 @@ function TagsInputWidget({
|
|
|
1873
2360
|
}) {
|
|
1874
2361
|
const error = firstError(rawErrors);
|
|
1875
2362
|
const tags = Array.isArray(value) ? value.map(String) : [];
|
|
1876
|
-
return /* @__PURE__ */
|
|
2363
|
+
return /* @__PURE__ */ jsx19(
|
|
1877
2364
|
FormTagsInput,
|
|
1878
2365
|
{
|
|
1879
2366
|
id,
|
|
@@ -1896,9 +2383,13 @@ function TagsInputWidget({
|
|
|
1896
2383
|
}
|
|
1897
2384
|
|
|
1898
2385
|
// src/widgets/JsonEditorWidget.tsx
|
|
1899
|
-
import { useCallback as useCallback4, useState as
|
|
1900
|
-
import {
|
|
1901
|
-
|
|
2386
|
+
import { Suspense, lazy, useCallback as useCallback4, useState as useState6 } from "react";
|
|
2387
|
+
import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2388
|
+
var JsonEditor = lazy(
|
|
2389
|
+
() => import("@petrarca/sonnet-ui/json-editor").then((m) => ({
|
|
2390
|
+
default: m.JsonEditor
|
|
2391
|
+
}))
|
|
2392
|
+
);
|
|
1902
2393
|
var FULL_DOCUMENT = ["/"];
|
|
1903
2394
|
function JsonEditorWidget({
|
|
1904
2395
|
id,
|
|
@@ -1909,7 +2400,7 @@ function JsonEditorWidget({
|
|
|
1909
2400
|
required,
|
|
1910
2401
|
options
|
|
1911
2402
|
}) {
|
|
1912
|
-
const [raw, setRaw] =
|
|
2403
|
+
const [raw, setRaw] = useState6(
|
|
1913
2404
|
value != null ? JSON.stringify(value, null, 2) : ""
|
|
1914
2405
|
);
|
|
1915
2406
|
const height = options?.height ?? "240px";
|
|
@@ -1924,42 +2415,104 @@ function JsonEditorWidget({
|
|
|
1924
2415
|
},
|
|
1925
2416
|
[onChange]
|
|
1926
2417
|
);
|
|
1927
|
-
return /* @__PURE__ */
|
|
1928
|
-
label && /* @__PURE__ */
|
|
2418
|
+
return /* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
2419
|
+
label && /* @__PURE__ */ jsxs13("label", { htmlFor: id, className: "text-sm font-medium leading-none", children: [
|
|
1929
2420
|
label,
|
|
1930
|
-
required && /* @__PURE__ */
|
|
2421
|
+
required && /* @__PURE__ */ jsx20("span", { className: "text-destructive ml-1", children: "*" })
|
|
1931
2422
|
] }),
|
|
1932
|
-
description && /* @__PURE__ */
|
|
1933
|
-
/* @__PURE__ */
|
|
1934
|
-
|
|
2423
|
+
description && /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: description }),
|
|
2424
|
+
/* @__PURE__ */ jsx20(
|
|
2425
|
+
Suspense,
|
|
1935
2426
|
{
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
2427
|
+
fallback: /* @__PURE__ */ jsx20(
|
|
2428
|
+
"div",
|
|
2429
|
+
{
|
|
2430
|
+
className: "rounded-md border bg-muted/30",
|
|
2431
|
+
style: { height },
|
|
2432
|
+
"aria-busy": true
|
|
2433
|
+
}
|
|
2434
|
+
),
|
|
2435
|
+
children: /* @__PURE__ */ jsx20(
|
|
2436
|
+
JsonEditor,
|
|
2437
|
+
{
|
|
2438
|
+
value: raw,
|
|
2439
|
+
editablePaths: FULL_DOCUMENT,
|
|
2440
|
+
onChange: handleChange,
|
|
2441
|
+
height,
|
|
2442
|
+
className: "rounded-md border overflow-hidden"
|
|
2443
|
+
}
|
|
2444
|
+
)
|
|
1941
2445
|
}
|
|
1942
2446
|
)
|
|
1943
2447
|
] });
|
|
1944
2448
|
}
|
|
1945
2449
|
|
|
1946
|
-
// src/widgets/
|
|
1947
|
-
import {
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1951
|
-
var VALID_MODES = ["eager", "typeahead", "explicit"];
|
|
1952
|
-
function resolveSelectMode(options) {
|
|
1953
|
-
const rawMode = options?.mode;
|
|
1954
|
-
if (typeof rawMode === "string" && VALID_MODES.includes(rawMode)) {
|
|
1955
|
-
return rawMode;
|
|
1956
|
-
}
|
|
1957
|
-
return "eager";
|
|
2450
|
+
// src/widgets/KeyValueWidget.tsx
|
|
2451
|
+
import { jsx as jsx21 } from "react/jsx-runtime";
|
|
2452
|
+
function displayOf(display) {
|
|
2453
|
+
return display === "rows" || display === "keys" ? display : "tags";
|
|
1958
2454
|
}
|
|
1959
|
-
function
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
2455
|
+
function asStringMap(value) {
|
|
2456
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2457
|
+
return Object.fromEntries(
|
|
2458
|
+
Object.entries(value).map(([k, v]) => [
|
|
2459
|
+
k,
|
|
2460
|
+
String(v)
|
|
2461
|
+
])
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
function KeyValueWidget({
|
|
2465
|
+
id,
|
|
2466
|
+
name,
|
|
2467
|
+
value,
|
|
2468
|
+
onChange,
|
|
2469
|
+
required,
|
|
2470
|
+
disabled,
|
|
2471
|
+
readonly,
|
|
2472
|
+
rawErrors,
|
|
2473
|
+
label,
|
|
2474
|
+
description,
|
|
2475
|
+
options
|
|
2476
|
+
}) {
|
|
2477
|
+
return /* @__PURE__ */ jsx21(
|
|
2478
|
+
FormKeyValueInput,
|
|
2479
|
+
{
|
|
2480
|
+
id,
|
|
2481
|
+
name,
|
|
2482
|
+
value: asStringMap(value),
|
|
2483
|
+
onChange: (next) => onChange(next),
|
|
2484
|
+
label,
|
|
2485
|
+
labelPlacement: options?.labelPlacement,
|
|
2486
|
+
description,
|
|
2487
|
+
required,
|
|
2488
|
+
disabled,
|
|
2489
|
+
readOnly: readonly,
|
|
2490
|
+
error: firstError(rawErrors),
|
|
2491
|
+
keyPlaceholder: options?.keyPlaceholder,
|
|
2492
|
+
valuePlaceholder: options?.valuePlaceholder,
|
|
2493
|
+
separator: options?.separator,
|
|
2494
|
+
display: displayOf(options?.display)
|
|
2495
|
+
}
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
// src/widgets/EntitySelectWidget.tsx
|
|
2500
|
+
import { useMemo as useMemo3 } from "react";
|
|
2501
|
+
import { EntitySelect } from "@petrarca/sonnet-ui";
|
|
2502
|
+
import { useFetcherFactory } from "@petrarca/sonnet-core/entityOptions";
|
|
2503
|
+
import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2504
|
+
var VALID_MODES = ["eager", "typeahead", "explicit"];
|
|
2505
|
+
function resolveSelectMode(options) {
|
|
2506
|
+
const rawMode = options?.mode;
|
|
2507
|
+
if (typeof rawMode === "string" && VALID_MODES.includes(rawMode)) {
|
|
2508
|
+
return rawMode;
|
|
2509
|
+
}
|
|
2510
|
+
return "eager";
|
|
2511
|
+
}
|
|
2512
|
+
function resolveMinChars(options) {
|
|
2513
|
+
const rawMinChars = options?.minChars;
|
|
2514
|
+
if (typeof rawMinChars === "number" && rawMinChars >= 1) return rawMinChars;
|
|
2515
|
+
return 1;
|
|
1963
2516
|
}
|
|
1964
2517
|
function buildRestConfig(options) {
|
|
1965
2518
|
if (!("endpoint" in options) || !options.endpoint) return null;
|
|
@@ -2019,13 +2572,13 @@ function EntitySelectWidget({
|
|
|
2019
2572
|
[fetcherConfig, name]
|
|
2020
2573
|
);
|
|
2021
2574
|
if (!fetcher) {
|
|
2022
|
-
return /* @__PURE__ */
|
|
2575
|
+
return /* @__PURE__ */ jsxs14("div", { className: "text-sm text-destructive", children: [
|
|
2023
2576
|
'EntitySelectWidget: missing endpoint or query in x-ui-options for field "',
|
|
2024
2577
|
name,
|
|
2025
2578
|
'"'
|
|
2026
2579
|
] });
|
|
2027
2580
|
}
|
|
2028
|
-
return /* @__PURE__ */
|
|
2581
|
+
return /* @__PURE__ */ jsx22(
|
|
2029
2582
|
EntitySelect,
|
|
2030
2583
|
{
|
|
2031
2584
|
id,
|
|
@@ -2071,7 +2624,7 @@ function useNestedFormContext() {
|
|
|
2071
2624
|
}
|
|
2072
2625
|
|
|
2073
2626
|
// src/templates/DefaultObjectContainerTemplate.tsx
|
|
2074
|
-
import { useState as
|
|
2627
|
+
import { useState as useState7 } from "react";
|
|
2075
2628
|
import { ChevronDown as ChevronDown2, ChevronRight as ChevronRight2 } from "lucide-react";
|
|
2076
2629
|
import { Card as Card2, CardContent, CardHeader, CardTitle } from "@petrarca/sonnet-ui";
|
|
2077
2630
|
import {
|
|
@@ -2079,18 +2632,18 @@ import {
|
|
|
2079
2632
|
CollapsibleContent as CollapsibleContent2,
|
|
2080
2633
|
CollapsibleTrigger
|
|
2081
2634
|
} from "@petrarca/sonnet-ui";
|
|
2082
|
-
import { jsx as
|
|
2635
|
+
import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2083
2636
|
function CardVariant({
|
|
2084
2637
|
children,
|
|
2085
2638
|
label,
|
|
2086
2639
|
description
|
|
2087
2640
|
}) {
|
|
2088
|
-
return /* @__PURE__ */
|
|
2089
|
-
label && /* @__PURE__ */
|
|
2090
|
-
/* @__PURE__ */
|
|
2091
|
-
description && /* @__PURE__ */
|
|
2641
|
+
return /* @__PURE__ */ jsxs15(Card2, { children: [
|
|
2642
|
+
label && /* @__PURE__ */ jsxs15(CardHeader, { className: "pb-3", children: [
|
|
2643
|
+
/* @__PURE__ */ jsx23(CardTitle, { className: "text-base", children: label }),
|
|
2644
|
+
description && /* @__PURE__ */ jsx23("p", { className: "text-sm text-muted-foreground", children: description })
|
|
2092
2645
|
] }),
|
|
2093
|
-
/* @__PURE__ */
|
|
2646
|
+
/* @__PURE__ */ jsx23(CardContent, { children })
|
|
2094
2647
|
] });
|
|
2095
2648
|
}
|
|
2096
2649
|
function SectionVariant({
|
|
@@ -2098,12 +2651,12 @@ function SectionVariant({
|
|
|
2098
2651
|
label,
|
|
2099
2652
|
description
|
|
2100
2653
|
}) {
|
|
2101
|
-
return /* @__PURE__ */
|
|
2102
|
-
label && /* @__PURE__ */
|
|
2103
|
-
/* @__PURE__ */
|
|
2104
|
-
description && /* @__PURE__ */
|
|
2654
|
+
return /* @__PURE__ */ jsxs15("div", { className: "space-y-3", children: [
|
|
2655
|
+
label && /* @__PURE__ */ jsxs15("div", { children: [
|
|
2656
|
+
/* @__PURE__ */ jsx23("h4", { className: "text-sm font-medium", children: label }),
|
|
2657
|
+
description && /* @__PURE__ */ jsx23("p", { className: "text-xs text-muted-foreground", children: description })
|
|
2105
2658
|
] }),
|
|
2106
|
-
/* @__PURE__ */
|
|
2659
|
+
/* @__PURE__ */ jsx23("div", { className: "pl-3 border-l-2 border-muted", children })
|
|
2107
2660
|
] });
|
|
2108
2661
|
}
|
|
2109
2662
|
function DefaultObjectContainerTemplate({
|
|
@@ -2115,10 +2668,10 @@ function DefaultObjectContainerTemplate({
|
|
|
2115
2668
|
}) {
|
|
2116
2669
|
switch (variant) {
|
|
2117
2670
|
case "card":
|
|
2118
|
-
return /* @__PURE__ */
|
|
2671
|
+
return /* @__PURE__ */ jsx23(CardVariant, { label, description, children });
|
|
2119
2672
|
case "collapsible":
|
|
2120
|
-
if (!label) return /* @__PURE__ */
|
|
2121
|
-
return /* @__PURE__ */
|
|
2673
|
+
if (!label) return /* @__PURE__ */ jsx23(SectionVariant, { children });
|
|
2674
|
+
return /* @__PURE__ */ jsx23(
|
|
2122
2675
|
CollapsibleVariant,
|
|
2123
2676
|
{
|
|
2124
2677
|
label,
|
|
@@ -2128,12 +2681,12 @@ function DefaultObjectContainerTemplate({
|
|
|
2128
2681
|
}
|
|
2129
2682
|
);
|
|
2130
2683
|
case "inline":
|
|
2131
|
-
return /* @__PURE__ */
|
|
2684
|
+
return /* @__PURE__ */ jsx23("div", { children });
|
|
2132
2685
|
case "borderless":
|
|
2133
|
-
return /* @__PURE__ */
|
|
2686
|
+
return /* @__PURE__ */ jsx23("div", { className: "space-y-4", children });
|
|
2134
2687
|
case "section":
|
|
2135
2688
|
default:
|
|
2136
|
-
return /* @__PURE__ */
|
|
2689
|
+
return /* @__PURE__ */ jsx23(SectionVariant, { label, description, children });
|
|
2137
2690
|
}
|
|
2138
2691
|
}
|
|
2139
2692
|
function CollapsibleVariant({
|
|
@@ -2142,26 +2695,26 @@ function CollapsibleVariant({
|
|
|
2142
2695
|
defaultOpen,
|
|
2143
2696
|
children
|
|
2144
2697
|
}) {
|
|
2145
|
-
const [open, setOpen] =
|
|
2146
|
-
return /* @__PURE__ */
|
|
2147
|
-
/* @__PURE__ */
|
|
2698
|
+
const [open, setOpen] = useState7(defaultOpen);
|
|
2699
|
+
return /* @__PURE__ */ jsxs15(Collapsible2, { open, onOpenChange: setOpen, children: [
|
|
2700
|
+
/* @__PURE__ */ jsx23(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs15(
|
|
2148
2701
|
"button",
|
|
2149
2702
|
{
|
|
2150
2703
|
type: "button",
|
|
2151
2704
|
className: "flex w-full items-center gap-2 text-sm font-medium hover:text-foreground/80",
|
|
2152
2705
|
children: [
|
|
2153
|
-
open ? /* @__PURE__ */
|
|
2706
|
+
open ? /* @__PURE__ */ jsx23(ChevronDown2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx23(ChevronRight2, { className: "h-4 w-4" }),
|
|
2154
2707
|
label
|
|
2155
2708
|
]
|
|
2156
2709
|
}
|
|
2157
2710
|
) }),
|
|
2158
|
-
description && /* @__PURE__ */
|
|
2159
|
-
/* @__PURE__ */
|
|
2711
|
+
description && /* @__PURE__ */ jsx23("p", { className: "text-xs text-muted-foreground ml-6", children: description }),
|
|
2712
|
+
/* @__PURE__ */ jsx23(CollapsibleContent2, { className: "mt-2 pl-3 border-l-2 border-muted", children })
|
|
2160
2713
|
] });
|
|
2161
2714
|
}
|
|
2162
2715
|
|
|
2163
2716
|
// src/widgets/ObjectWidget.tsx
|
|
2164
|
-
import { Fragment as Fragment3, jsx as
|
|
2717
|
+
import { Fragment as Fragment3, jsx as jsx24 } from "react/jsx-runtime";
|
|
2165
2718
|
function buildSubSchema(prop) {
|
|
2166
2719
|
const properties = prop.properties;
|
|
2167
2720
|
if (!properties) return null;
|
|
@@ -2227,7 +2780,7 @@ function ObjectWidget({
|
|
|
2227
2780
|
globalOptions,
|
|
2228
2781
|
layout
|
|
2229
2782
|
);
|
|
2230
|
-
const content = /* @__PURE__ */
|
|
2783
|
+
const content = /* @__PURE__ */ jsx24(
|
|
2231
2784
|
Renderer,
|
|
2232
2785
|
{
|
|
2233
2786
|
schema: subSchema,
|
|
@@ -2242,8 +2795,8 @@ function ObjectWidget({
|
|
|
2242
2795
|
onBlur: onBlur ? () => onBlur(id, value) : void 0
|
|
2243
2796
|
}
|
|
2244
2797
|
);
|
|
2245
|
-
if (displayContents) return /* @__PURE__ */
|
|
2246
|
-
return /* @__PURE__ */
|
|
2798
|
+
if (displayContents) return /* @__PURE__ */ jsx24(Fragment3, { children: content });
|
|
2799
|
+
return /* @__PURE__ */ jsx24(
|
|
2247
2800
|
ContainerTemplate,
|
|
2248
2801
|
{
|
|
2249
2802
|
label,
|
|
@@ -2257,15 +2810,15 @@ function ObjectWidget({
|
|
|
2257
2810
|
|
|
2258
2811
|
// src/widgets/ArrayWidget.tsx
|
|
2259
2812
|
import { useCallback as useCallback5, useMemo as useMemo5 } from "react";
|
|
2260
|
-
import { Plus as
|
|
2261
|
-
import { Button as
|
|
2813
|
+
import { Plus as Plus3 } from "lucide-react";
|
|
2814
|
+
import { Button as Button6 } from "@petrarca/sonnet-ui";
|
|
2262
2815
|
import { applySchemaDefaults } from "@petrarca/sonnet-core/schema";
|
|
2263
2816
|
import { FORM_ITEM_ID_FIELD, generateId } from "@petrarca/sonnet-core";
|
|
2264
2817
|
|
|
2265
2818
|
// src/templates/DefaultArrayItemTemplate.tsx
|
|
2266
2819
|
import { Trash2, ChevronUp, ChevronDown as ChevronDown3 } from "lucide-react";
|
|
2267
|
-
import { Button as
|
|
2268
|
-
import { jsx as
|
|
2820
|
+
import { Button as Button5 } from "@petrarca/sonnet-ui";
|
|
2821
|
+
import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2269
2822
|
function DefaultArrayItemTemplate({
|
|
2270
2823
|
children,
|
|
2271
2824
|
index,
|
|
@@ -2279,16 +2832,16 @@ function DefaultArrayItemTemplate({
|
|
|
2279
2832
|
onMoveDown
|
|
2280
2833
|
}) {
|
|
2281
2834
|
const showControls = canRemove || orderable;
|
|
2282
|
-
return /* @__PURE__ */
|
|
2835
|
+
return /* @__PURE__ */ jsxs16(
|
|
2283
2836
|
"div",
|
|
2284
2837
|
{
|
|
2285
2838
|
"data-fid": fid,
|
|
2286
2839
|
className: "relative flex gap-2 rounded-md border border-border/50 bg-muted/20 p-3",
|
|
2287
2840
|
children: [
|
|
2288
|
-
/* @__PURE__ */
|
|
2289
|
-
showControls && /* @__PURE__ */
|
|
2290
|
-
orderable && index > 0 && /* @__PURE__ */
|
|
2291
|
-
|
|
2841
|
+
/* @__PURE__ */ jsx25("div", { className: "flex-1 min-w-0", children }),
|
|
2842
|
+
showControls && /* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-1 pt-0.5", children: [
|
|
2843
|
+
orderable && index > 0 && /* @__PURE__ */ jsx25(
|
|
2844
|
+
Button5,
|
|
2292
2845
|
{
|
|
2293
2846
|
type: "button",
|
|
2294
2847
|
variant: "ghost",
|
|
@@ -2296,11 +2849,11 @@ function DefaultArrayItemTemplate({
|
|
|
2296
2849
|
disabled,
|
|
2297
2850
|
onClick: onMoveUp,
|
|
2298
2851
|
title: "Move up",
|
|
2299
|
-
children: /* @__PURE__ */
|
|
2852
|
+
children: /* @__PURE__ */ jsx25(ChevronUp, { className: "h-3.5 w-3.5" })
|
|
2300
2853
|
}
|
|
2301
2854
|
),
|
|
2302
|
-
orderable && index < total - 1 && /* @__PURE__ */
|
|
2303
|
-
|
|
2855
|
+
orderable && index < total - 1 && /* @__PURE__ */ jsx25(
|
|
2856
|
+
Button5,
|
|
2304
2857
|
{
|
|
2305
2858
|
type: "button",
|
|
2306
2859
|
variant: "ghost",
|
|
@@ -2308,11 +2861,11 @@ function DefaultArrayItemTemplate({
|
|
|
2308
2861
|
disabled,
|
|
2309
2862
|
onClick: onMoveDown,
|
|
2310
2863
|
title: "Move down",
|
|
2311
|
-
children: /* @__PURE__ */
|
|
2864
|
+
children: /* @__PURE__ */ jsx25(ChevronDown3, { className: "h-3.5 w-3.5" })
|
|
2312
2865
|
}
|
|
2313
2866
|
),
|
|
2314
|
-
canRemove && /* @__PURE__ */
|
|
2315
|
-
|
|
2867
|
+
canRemove && /* @__PURE__ */ jsx25(
|
|
2868
|
+
Button5,
|
|
2316
2869
|
{
|
|
2317
2870
|
type: "button",
|
|
2318
2871
|
variant: "ghost",
|
|
@@ -2321,7 +2874,7 @@ function DefaultArrayItemTemplate({
|
|
|
2321
2874
|
onClick: onRemove,
|
|
2322
2875
|
title: "Remove item",
|
|
2323
2876
|
className: "text-muted-foreground hover:text-destructive",
|
|
2324
|
-
children: /* @__PURE__ */
|
|
2877
|
+
children: /* @__PURE__ */ jsx25(Trash2, { className: "h-3.5 w-3.5" })
|
|
2325
2878
|
}
|
|
2326
2879
|
)
|
|
2327
2880
|
] })
|
|
@@ -2338,7 +2891,7 @@ function buildGridTemplate(layout, canRemove, fieldCount, orderable = false) {
|
|
|
2338
2891
|
}
|
|
2339
2892
|
|
|
2340
2893
|
// src/templates/DefaultArrayHeaderTemplate.tsx
|
|
2341
|
-
import { jsx as
|
|
2894
|
+
import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2342
2895
|
function DefaultArrayHeaderTemplate({
|
|
2343
2896
|
itemSchema,
|
|
2344
2897
|
layout,
|
|
@@ -2352,7 +2905,7 @@ function DefaultArrayHeaderTemplate({
|
|
|
2352
2905
|
const properties = itemSchema.properties ?? {};
|
|
2353
2906
|
const order = itemSchema["x-ui-order"];
|
|
2354
2907
|
const fieldNames = order ? order.filter((k) => k in properties) : Object.keys(properties);
|
|
2355
|
-
return /* @__PURE__ */
|
|
2908
|
+
return /* @__PURE__ */ jsxs17(
|
|
2356
2909
|
"div",
|
|
2357
2910
|
{
|
|
2358
2911
|
style: {
|
|
@@ -2361,16 +2914,16 @@ function DefaultArrayHeaderTemplate({
|
|
|
2361
2914
|
gap: `${gap}px`
|
|
2362
2915
|
},
|
|
2363
2916
|
children: [
|
|
2364
|
-
fieldNames.map((name) => /* @__PURE__ */
|
|
2365
|
-
orderable && /* @__PURE__ */
|
|
2366
|
-
canRemove && /* @__PURE__ */
|
|
2917
|
+
fieldNames.map((name) => /* @__PURE__ */ jsx26("span", { className: "text-xs font-medium text-muted-foreground", children: properties[name]?.title ?? name }, name)),
|
|
2918
|
+
orderable && /* @__PURE__ */ jsx26("span", {}),
|
|
2919
|
+
canRemove && /* @__PURE__ */ jsx26("span", {})
|
|
2367
2920
|
]
|
|
2368
2921
|
}
|
|
2369
2922
|
);
|
|
2370
2923
|
}
|
|
2371
2924
|
|
|
2372
2925
|
// src/widgets/ArrayWidget.tsx
|
|
2373
|
-
import { jsx as
|
|
2926
|
+
import { jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2374
2927
|
function countFields(itemSchema) {
|
|
2375
2928
|
return Object.keys(itemSchema.properties ?? {}).length;
|
|
2376
2929
|
}
|
|
@@ -2409,9 +2962,9 @@ function buildSubGlobalOptions(layout, isHorizontal) {
|
|
|
2409
2962
|
}
|
|
2410
2963
|
function ArrayHeader({ label, description }) {
|
|
2411
2964
|
if (!label) return null;
|
|
2412
|
-
return /* @__PURE__ */
|
|
2413
|
-
/* @__PURE__ */
|
|
2414
|
-
description && /* @__PURE__ */
|
|
2965
|
+
return /* @__PURE__ */ jsxs18("div", { children: [
|
|
2966
|
+
/* @__PURE__ */ jsx27("h4", { className: "text-sm font-medium", children: label }),
|
|
2967
|
+
description && /* @__PURE__ */ jsx27("p", { className: "text-xs text-muted-foreground", children: description })
|
|
2415
2968
|
] });
|
|
2416
2969
|
}
|
|
2417
2970
|
function useArrayHandlers(items, onChange, propertySchema) {
|
|
@@ -2459,19 +3012,20 @@ function ArrayWidget({
|
|
|
2459
3012
|
description,
|
|
2460
3013
|
options,
|
|
2461
3014
|
schema,
|
|
2462
|
-
name
|
|
3015
|
+
name,
|
|
3016
|
+
onBlur
|
|
2463
3017
|
}) {
|
|
2464
3018
|
const { templates } = useNestedFormContext();
|
|
2465
3019
|
const items = useMemo5(() => ensureItemIds(value), [value]);
|
|
2466
3020
|
const { handleItemChange, handleAdd, handleRemove, handleMove } = useArrayHandlers(items, onChange, propertySchema);
|
|
2467
3021
|
const itemSchema = propertySchema.items;
|
|
2468
3022
|
if (!itemSchema?.properties) {
|
|
2469
|
-
return /* @__PURE__ */
|
|
3023
|
+
return /* @__PURE__ */ jsx27("p", { className: "text-sm text-destructive", children: 'ArrayWidget requires items with type "object" and properties.' });
|
|
2470
3024
|
}
|
|
2471
3025
|
const layout = resolveLayout2(uiSchema, propertySchema);
|
|
2472
3026
|
const isHorizontal = layout.direction === "horizontal";
|
|
2473
3027
|
const subGlobalOptions = buildSubGlobalOptions(layout, isHorizontal);
|
|
2474
|
-
return /* @__PURE__ */
|
|
3028
|
+
return /* @__PURE__ */ jsx27(
|
|
2475
3029
|
ArrayWidgetInner,
|
|
2476
3030
|
{
|
|
2477
3031
|
items,
|
|
@@ -2493,7 +3047,8 @@ function ArrayWidget({
|
|
|
2493
3047
|
onItemChange: handleItemChange,
|
|
2494
3048
|
onAdd: handleAdd,
|
|
2495
3049
|
onRemove: handleRemove,
|
|
2496
|
-
onMove: handleMove
|
|
3050
|
+
onMove: handleMove,
|
|
3051
|
+
onBlur
|
|
2497
3052
|
}
|
|
2498
3053
|
);
|
|
2499
3054
|
}
|
|
@@ -2553,7 +3108,8 @@ function ArrayWidgetInner({
|
|
|
2553
3108
|
onItemChange,
|
|
2554
3109
|
onAdd,
|
|
2555
3110
|
onRemove,
|
|
2556
|
-
onMove
|
|
3111
|
+
onMove,
|
|
3112
|
+
onBlur
|
|
2557
3113
|
}) {
|
|
2558
3114
|
const constraints = resolveConstraints(
|
|
2559
3115
|
propertySchema,
|
|
@@ -2570,10 +3126,10 @@ function ArrayWidgetInner({
|
|
|
2570
3126
|
);
|
|
2571
3127
|
const fieldCount = countFields(itemSchema);
|
|
2572
3128
|
const itemOptions = buildItemOptions(isHorizontal, subGlobalOptions);
|
|
2573
|
-
return /* @__PURE__ */
|
|
2574
|
-
/* @__PURE__ */
|
|
2575
|
-
items.length === 0 && display.emptyMessage && /* @__PURE__ */
|
|
2576
|
-
items.length > 0 && /* @__PURE__ */
|
|
3129
|
+
return /* @__PURE__ */ jsxs18("div", { className: isHorizontal ? "space-y-1" : "space-y-3", children: [
|
|
3130
|
+
/* @__PURE__ */ jsx27(ArrayHeader, { label, description }),
|
|
3131
|
+
items.length === 0 && display.emptyMessage && /* @__PURE__ */ jsx27("p", { className: "text-sm text-muted-foreground italic", children: display.emptyMessage }),
|
|
3132
|
+
items.length > 0 && /* @__PURE__ */ jsx27(
|
|
2577
3133
|
HeaderTemplate,
|
|
2578
3134
|
{
|
|
2579
3135
|
itemSchema,
|
|
@@ -2583,7 +3139,7 @@ function ArrayWidgetInner({
|
|
|
2583
3139
|
fieldCount
|
|
2584
3140
|
}
|
|
2585
3141
|
),
|
|
2586
|
-
items.map((item, index) => /* @__PURE__ */
|
|
3142
|
+
items.map((item, index) => /* @__PURE__ */ jsx27(
|
|
2587
3143
|
ActiveItemTemplate,
|
|
2588
3144
|
{
|
|
2589
3145
|
index,
|
|
@@ -2597,7 +3153,7 @@ function ArrayWidgetInner({
|
|
|
2597
3153
|
onRemove: () => onRemove(index),
|
|
2598
3154
|
onMoveUp: index > 0 ? () => onMove(index, index - 1) : void 0,
|
|
2599
3155
|
onMoveDown: index < items.length - 1 ? () => onMove(index, index + 1) : void 0,
|
|
2600
|
-
children: /* @__PURE__ */
|
|
3156
|
+
children: /* @__PURE__ */ jsx27(
|
|
2601
3157
|
ObjectWidget,
|
|
2602
3158
|
{
|
|
2603
3159
|
id: `field-${name}-${index}`,
|
|
@@ -2612,14 +3168,15 @@ function ArrayWidgetInner({
|
|
|
2612
3168
|
description: void 0,
|
|
2613
3169
|
disabled,
|
|
2614
3170
|
readonly,
|
|
2615
|
-
options: itemOptions
|
|
3171
|
+
options: itemOptions,
|
|
3172
|
+
onBlur
|
|
2616
3173
|
}
|
|
2617
3174
|
)
|
|
2618
3175
|
},
|
|
2619
3176
|
item[FORM_ITEM_ID_FIELD]
|
|
2620
3177
|
)),
|
|
2621
|
-
canAdd && /* @__PURE__ */
|
|
2622
|
-
|
|
3178
|
+
canAdd && /* @__PURE__ */ jsxs18(
|
|
3179
|
+
Button6,
|
|
2623
3180
|
{
|
|
2624
3181
|
type: "button",
|
|
2625
3182
|
variant: "outline",
|
|
@@ -2628,7 +3185,7 @@ function ArrayWidgetInner({
|
|
|
2628
3185
|
disabled,
|
|
2629
3186
|
className: "gap-1.5",
|
|
2630
3187
|
children: [
|
|
2631
|
-
/* @__PURE__ */
|
|
3188
|
+
/* @__PURE__ */ jsx27(Plus3, { className: "h-3.5 w-3.5" }),
|
|
2632
3189
|
display.addLabel
|
|
2633
3190
|
]
|
|
2634
3191
|
}
|
|
@@ -2637,13 +3194,25 @@ function ArrayWidgetInner({
|
|
|
2637
3194
|
}
|
|
2638
3195
|
|
|
2639
3196
|
// src/widgets/QuantityWidget.tsx
|
|
2640
|
-
import { useCallback as useCallback6, useRef as useRef4 } from "react";
|
|
2641
|
-
import {
|
|
3197
|
+
import { useCallback as useCallback6, useRef as useRef4, useState as useState8 } from "react";
|
|
3198
|
+
import { unitRangesOf } from "@petrarca/sonnet-core/schema";
|
|
3199
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
2642
3200
|
function isUnitOption(u) {
|
|
2643
3201
|
return typeof u === "object" && u !== null && typeof u.value === "string" && typeof u.label === "string";
|
|
2644
3202
|
}
|
|
2645
|
-
function
|
|
2646
|
-
const
|
|
3203
|
+
function schemaUnits(propertySchema) {
|
|
3204
|
+
const codes = propertySchema.properties?.unit?.enum;
|
|
3205
|
+
return Array.isArray(codes) ? codes.map((c) => ({ value: String(c), label: String(c) })) : [];
|
|
3206
|
+
}
|
|
3207
|
+
function withSchemaRanges(units, propertySchema) {
|
|
3208
|
+
const ranges = unitRangesOf(propertySchema) ?? {};
|
|
3209
|
+
return units.map((u) => {
|
|
3210
|
+
const range = ranges[u.value];
|
|
3211
|
+
return range ? { ...u, min: range.min, max: range.max } : u;
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
function parseQuantityOptions(options, fallbackUnits) {
|
|
3215
|
+
const units = Array.isArray(options.units) ? options.units.filter(isUnitOption) : fallbackUnits;
|
|
2647
3216
|
const precision = typeof options.precision === "number" ? options.precision : void 0;
|
|
2648
3217
|
const step = typeof options.step === "number" ? options.step : void 0;
|
|
2649
3218
|
const unitPosition = options.unitPosition === "prefix" || options.unitPosition === "suffix" ? options.unitPosition : void 0;
|
|
@@ -2668,47 +3237,1169 @@ function QuantityWidget({
|
|
|
2668
3237
|
}) {
|
|
2669
3238
|
const error = firstError(rawErrors);
|
|
2670
3239
|
const {
|
|
2671
|
-
units,
|
|
3240
|
+
units: optionUnits,
|
|
2672
3241
|
precision,
|
|
2673
3242
|
step,
|
|
2674
3243
|
unitPosition: unitPositionOpt
|
|
2675
|
-
} = parseQuantityOptions(options);
|
|
2676
|
-
const
|
|
3244
|
+
} = parseQuantityOptions(options, schemaUnits(propertySchema));
|
|
3245
|
+
const units = withSchemaRanges(optionUnits, propertySchema);
|
|
3246
|
+
const [pendingUnit, setPendingUnit] = useState8();
|
|
3247
|
+
const stored = value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3248
|
+
const quantityValue = stored ?? (pendingUnit ? { value: void 0, unit: pendingUnit } : void 0);
|
|
3249
|
+
const handleChange = useCallback6(
|
|
3250
|
+
(next) => {
|
|
3251
|
+
if (next.value !== void 0) return onChange(next);
|
|
3252
|
+
setPendingUnit(next.unit);
|
|
3253
|
+
onChange(void 0);
|
|
3254
|
+
},
|
|
3255
|
+
[onChange]
|
|
3256
|
+
);
|
|
2677
3257
|
const valueRef = useRef4(value);
|
|
2678
3258
|
valueRef.current = value;
|
|
2679
3259
|
const handleBlur = useCallback6(() => {
|
|
2680
3260
|
onBlur?.(id, valueRef.current);
|
|
2681
3261
|
}, [id, onBlur]);
|
|
2682
|
-
return /* @__PURE__ */
|
|
3262
|
+
return /* @__PURE__ */ jsx28(
|
|
2683
3263
|
FormQuantityInput,
|
|
2684
3264
|
{
|
|
2685
|
-
id,
|
|
2686
|
-
name,
|
|
2687
|
-
value: quantityValue,
|
|
2688
|
-
onChange,
|
|
2689
|
-
onBlur: handleBlur,
|
|
2690
|
-
units,
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
3265
|
+
id,
|
|
3266
|
+
name,
|
|
3267
|
+
value: quantityValue,
|
|
3268
|
+
onChange: handleChange,
|
|
3269
|
+
onBlur: handleBlur,
|
|
3270
|
+
units,
|
|
3271
|
+
freeUnit: propertySchema.properties?.unit !== void 0,
|
|
3272
|
+
unitPosition: unitPositionOpt,
|
|
3273
|
+
precision,
|
|
3274
|
+
step,
|
|
3275
|
+
min: propertySchema.minimum,
|
|
3276
|
+
max: propertySchema.maximum,
|
|
3277
|
+
label,
|
|
3278
|
+
labelPlacement: options?.labelPlacement,
|
|
3279
|
+
description,
|
|
3280
|
+
error,
|
|
3281
|
+
placeholder,
|
|
3282
|
+
required,
|
|
3283
|
+
disabled,
|
|
3284
|
+
readOnly: readonly,
|
|
3285
|
+
autoFocus: autofocus
|
|
3286
|
+
}
|
|
3287
|
+
);
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
// src/widgets/CodedValueWidget.tsx
|
|
3291
|
+
import * as React13 from "react";
|
|
3292
|
+
import { ChevronsUpDown as ChevronsUpDown4, Loader2, TriangleAlert, X as X5 } from "lucide-react";
|
|
3293
|
+
import { cn as cn12, EMPTY_VALUE } from "@petrarca/sonnet-core";
|
|
3294
|
+
import {
|
|
3295
|
+
bindingOf,
|
|
3296
|
+
codingKey as codingKey5
|
|
3297
|
+
} from "@petrarca/sonnet-core/schema";
|
|
3298
|
+
import {
|
|
3299
|
+
useTerminology
|
|
3300
|
+
} from "@petrarca/sonnet-core/terminology";
|
|
3301
|
+
import {
|
|
3302
|
+
Badge as Badge4,
|
|
3303
|
+
Button as Button7,
|
|
3304
|
+
CodedValueDisplay as CodedValueDisplay2,
|
|
3305
|
+
Popover as Popover3,
|
|
3306
|
+
PopoverContent as PopoverContent3,
|
|
3307
|
+
PopoverTrigger as PopoverTrigger3
|
|
3308
|
+
} from "@petrarca/sonnet-ui";
|
|
3309
|
+
|
|
3310
|
+
// src/widgets/codedValue/codedValueModel.ts
|
|
3311
|
+
import {
|
|
3312
|
+
codingKey
|
|
3313
|
+
} from "@petrarca/sonnet-core/schema";
|
|
3314
|
+
var CODING_KEYS = ["system", "code", "version", "display"];
|
|
3315
|
+
function shapeOf(prop) {
|
|
3316
|
+
if (prop?.type === "string") return "code";
|
|
3317
|
+
return prop?.anyOf?.some((m) => m.type === "string") ? "codingOrText" : "coding";
|
|
3318
|
+
}
|
|
3319
|
+
function fieldModelOf(prop, options) {
|
|
3320
|
+
const multiple = prop.type === "array";
|
|
3321
|
+
const keep = Array.isArray(options?.keep) ? options.keep.filter(
|
|
3322
|
+
(k) => typeof k === "string"
|
|
3323
|
+
) : [];
|
|
3324
|
+
return {
|
|
3325
|
+
multiple,
|
|
3326
|
+
shape: shapeOf(multiple ? prop.items : prop),
|
|
3327
|
+
store: typeof options?.store === "string" ? options.store : "code",
|
|
3328
|
+
keep: keep.filter((k) => !CODING_KEYS.includes(k))
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
function takesText(field, binding) {
|
|
3332
|
+
if (field.shape === "codingOrText") return true;
|
|
3333
|
+
return field.shape === "code" && binding?.strength === "extensible";
|
|
3334
|
+
}
|
|
3335
|
+
function asCoding(value) {
|
|
3336
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
3337
|
+
const c = value;
|
|
3338
|
+
return typeof c.code === "string" && c.code ? { ...c, system: c.system ?? "", code: c.code } : void 0;
|
|
3339
|
+
}
|
|
3340
|
+
function entryOf(value, field, binding) {
|
|
3341
|
+
if (typeof value !== "string") {
|
|
3342
|
+
const coding = asCoding(value);
|
|
3343
|
+
return coding && { coding };
|
|
3344
|
+
}
|
|
3345
|
+
if (!value) return void 0;
|
|
3346
|
+
if (field.shape === "codingOrText") return { text: value };
|
|
3347
|
+
return {
|
|
3348
|
+
coding: {
|
|
3349
|
+
system: binding?.system ?? "",
|
|
3350
|
+
code: value,
|
|
3351
|
+
...binding?.version && { version: binding.version }
|
|
3352
|
+
}
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
function entriesOf(value, field, binding) {
|
|
3356
|
+
const values = field.multiple ? Array.isArray(value) ? value : [] : [value];
|
|
3357
|
+
return values.map((v) => entryOf(v, field, binding)).filter((e) => e !== void 0);
|
|
3358
|
+
}
|
|
3359
|
+
function entryKey(e) {
|
|
3360
|
+
return e.coding ? codingKey(e.coding) : `text:${e.text ?? ""}`;
|
|
3361
|
+
}
|
|
3362
|
+
function toStored(hit, field, binding) {
|
|
3363
|
+
if (field.shape === "code") return String(hit[field.store] ?? hit.code);
|
|
3364
|
+
const version = hit.version ?? binding?.version;
|
|
3365
|
+
const kept = field.keep.filter((k) => hit[k] != null);
|
|
3366
|
+
return {
|
|
3367
|
+
system: hit.system,
|
|
3368
|
+
code: hit.code,
|
|
3369
|
+
...version && { version },
|
|
3370
|
+
...hit.display && { display: hit.display },
|
|
3371
|
+
...Object.fromEntries(kept.map((k) => [k, hit[k]]))
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
function storedKey(v, field, binding) {
|
|
3375
|
+
const entry = entryOf(v, field, binding);
|
|
3376
|
+
return entry ? entryKey(entry) : void 0;
|
|
3377
|
+
}
|
|
3378
|
+
function withAnswer(value, answer, field, binding) {
|
|
3379
|
+
if (!field.multiple) return answer;
|
|
3380
|
+
const current = Array.isArray(value) ? value : [];
|
|
3381
|
+
const key = storedKey(answer, field, binding);
|
|
3382
|
+
const others = current.filter((v) => storedKey(v, field, binding) !== key);
|
|
3383
|
+
if (others.length < current.length) return others.length ? others : void 0;
|
|
3384
|
+
return [...current, answer];
|
|
3385
|
+
}
|
|
3386
|
+
function withoutAnswer(value, key, field, binding) {
|
|
3387
|
+
if (!field.multiple) return void 0;
|
|
3388
|
+
const current = Array.isArray(value) ? value : [];
|
|
3389
|
+
const others = current.filter((v) => storedKey(v, field, binding) !== key);
|
|
3390
|
+
return others.length ? others : void 0;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
// src/widgets/codedValue/useCodedValueOptions.ts
|
|
3394
|
+
import { useCallback as useCallback7, useEffect as useEffect4, useRef as useRef5, useState as useState9 } from "react";
|
|
3395
|
+
var PAGE_SIZE = 50;
|
|
3396
|
+
var DEBOUNCE_MS = 250;
|
|
3397
|
+
function useDebounced(text) {
|
|
3398
|
+
const [settled, setSettled] = useState9(text);
|
|
3399
|
+
useEffect4(() => {
|
|
3400
|
+
const t = setTimeout(() => setSettled(text), DEBOUNCE_MS);
|
|
3401
|
+
return () => clearTimeout(t);
|
|
3402
|
+
}, [text]);
|
|
3403
|
+
return settled;
|
|
3404
|
+
}
|
|
3405
|
+
function fitsOnePage(result) {
|
|
3406
|
+
return result.total !== void 0 ? result.total <= PAGE_SIZE : result.codings.length < PAGE_SIZE;
|
|
3407
|
+
}
|
|
3408
|
+
function moreAfter(result) {
|
|
3409
|
+
return result.total !== void 0 ? result.codings.length < result.total : false;
|
|
3410
|
+
}
|
|
3411
|
+
function usePagedSearch(service, list) {
|
|
3412
|
+
const [page, setPage] = useState9();
|
|
3413
|
+
const [loading, setLoading] = useState9(false);
|
|
3414
|
+
const [error, setError] = useState9();
|
|
3415
|
+
const request = useRef5(0);
|
|
3416
|
+
const shown = useRef5(null);
|
|
3417
|
+
const run = useCallback7(
|
|
3418
|
+
(text, offset, before) => {
|
|
3419
|
+
if (!service || !list) return Promise.resolve(void 0);
|
|
3420
|
+
const id = ++request.current;
|
|
3421
|
+
setLoading(true);
|
|
3422
|
+
return service.search({ ...list, text, limit: PAGE_SIZE, offset }).then((r) => {
|
|
3423
|
+
if (id !== request.current) return void 0;
|
|
3424
|
+
shown.current = text ?? "";
|
|
3425
|
+
const result = { ...r, codings: [...before, ...r.codings] };
|
|
3426
|
+
setPage({ result, hasMore: moreAfter(result) });
|
|
3427
|
+
setError(void 0);
|
|
3428
|
+
return r;
|
|
3429
|
+
}).catch((e) => {
|
|
3430
|
+
if (id === request.current) setError(String(e));
|
|
3431
|
+
return void 0;
|
|
3432
|
+
}).finally(() => {
|
|
3433
|
+
if (id === request.current) setLoading(false);
|
|
3434
|
+
});
|
|
3435
|
+
},
|
|
3436
|
+
[service, list]
|
|
3437
|
+
);
|
|
3438
|
+
const clear = useCallback7(() => {
|
|
3439
|
+
request.current++;
|
|
3440
|
+
shown.current = null;
|
|
3441
|
+
setPage(void 0);
|
|
3442
|
+
setLoading(false);
|
|
3443
|
+
}, []);
|
|
3444
|
+
const isShown = useCallback7((text) => shown.current === text, []);
|
|
3445
|
+
return { page, loading, error, run, clear, isShown };
|
|
3446
|
+
}
|
|
3447
|
+
function useCodedValueOptions(service, list, forced, minChars = 1) {
|
|
3448
|
+
const [search, setSearch] = useState9("");
|
|
3449
|
+
const [decided, setDecided] = useState9(forced);
|
|
3450
|
+
const text = useDebounced(search.trim());
|
|
3451
|
+
const { page, loading, error, run, clear, isShown } = usePagedSearch(
|
|
3452
|
+
service,
|
|
3453
|
+
list
|
|
3454
|
+
);
|
|
3455
|
+
const mode = forced ?? decided;
|
|
3456
|
+
useEffect4(() => {
|
|
3457
|
+
if (forced) return;
|
|
3458
|
+
void run(void 0, 0, []).then(
|
|
3459
|
+
(r) => r && setDecided(fitsOnePage(r) ? "list" : "search")
|
|
3460
|
+
);
|
|
3461
|
+
}, [forced, run]);
|
|
3462
|
+
useEffect4(() => {
|
|
3463
|
+
if (!mode) return;
|
|
3464
|
+
if (mode === "search" && text.length < Math.max(minChars, 1)) clear();
|
|
3465
|
+
else if (!isShown(text)) void run(text || void 0, 0, []);
|
|
3466
|
+
}, [mode, text, minChars, run, clear, isShown]);
|
|
3467
|
+
const loadMore = useCallback7(() => {
|
|
3468
|
+
const loaded = page?.result.codings ?? [];
|
|
3469
|
+
void run(text || void 0, loaded.length, loaded);
|
|
3470
|
+
}, [page, run, text]);
|
|
3471
|
+
return {
|
|
3472
|
+
hits: page?.result.codings ?? [],
|
|
3473
|
+
total: page?.result.total,
|
|
3474
|
+
hasMore: page?.hasMore ?? false,
|
|
3475
|
+
loading,
|
|
3476
|
+
error,
|
|
3477
|
+
mode,
|
|
3478
|
+
search,
|
|
3479
|
+
setSearch,
|
|
3480
|
+
minChars,
|
|
3481
|
+
loadMore
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
// src/widgets/codedValue/useStoredLabel.ts
|
|
3486
|
+
import { useEffect as useEffect5, useState as useState10 } from "react";
|
|
3487
|
+
import {
|
|
3488
|
+
codingKey as codingKey2
|
|
3489
|
+
} from "@petrarca/sonnet-core/schema";
|
|
3490
|
+
async function fetchLabel(service, coding, valueSet, version) {
|
|
3491
|
+
if (coding.system) return (await service.lookup(coding))?.display;
|
|
3492
|
+
if (!valueSet) return void 0;
|
|
3493
|
+
const { codings } = await service.search({
|
|
3494
|
+
valueSet,
|
|
3495
|
+
version,
|
|
3496
|
+
text: coding.code
|
|
3497
|
+
});
|
|
3498
|
+
return codings.find((c) => c.code === coding.code)?.display;
|
|
3499
|
+
}
|
|
3500
|
+
function labelQuery(coding, binding) {
|
|
3501
|
+
const system = coding?.system ?? "";
|
|
3502
|
+
const code = coding?.code ?? "";
|
|
3503
|
+
return {
|
|
3504
|
+
own: coding?.display,
|
|
3505
|
+
system,
|
|
3506
|
+
code,
|
|
3507
|
+
version: coding?.version ?? binding?.version,
|
|
3508
|
+
valueSet: binding?.valueSet,
|
|
3509
|
+
key: codingKey2({ system, code })
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
function useStoredLabel(service, coding, binding) {
|
|
3513
|
+
const [fetched, setFetched] = useState10();
|
|
3514
|
+
const { own, system, code, version, valueSet, key } = labelQuery(
|
|
3515
|
+
coding,
|
|
3516
|
+
binding
|
|
3517
|
+
);
|
|
3518
|
+
useEffect5(() => {
|
|
3519
|
+
if (!service || !code || own) return;
|
|
3520
|
+
let live = true;
|
|
3521
|
+
fetchLabel(service, { system, code, version }, valueSet, version).then((label) => live && setFetched({ key, label })).catch(() => live && setFetched({ key }));
|
|
3522
|
+
return () => {
|
|
3523
|
+
live = false;
|
|
3524
|
+
};
|
|
3525
|
+
}, [service, system, code, version, valueSet, own, key]);
|
|
3526
|
+
return own ?? (fetched?.key === key ? fetched.label : void 0);
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
// src/widgets/codedValue/useListCheck.ts
|
|
3530
|
+
import { useEffect as useEffect6, useMemo as useMemo6, useState as useState11 } from "react";
|
|
3531
|
+
import {
|
|
3532
|
+
codingKey as codingKey3
|
|
3533
|
+
} from "@petrarca/sonnet-core/schema";
|
|
3534
|
+
var NONE = /* @__PURE__ */ new Map();
|
|
3535
|
+
function checks(binding, storesCodes) {
|
|
3536
|
+
return binding?.strength === "required" && !!(binding.valueSet || binding.system) && storesCodes;
|
|
3537
|
+
}
|
|
3538
|
+
async function warningOf(validate, binding, coding) {
|
|
3539
|
+
try {
|
|
3540
|
+
const result = await validate({
|
|
3541
|
+
valueSet: binding.valueSet,
|
|
3542
|
+
system: binding.system,
|
|
3543
|
+
version: binding.version,
|
|
3544
|
+
code: coding.code
|
|
3545
|
+
});
|
|
3546
|
+
if (result.inList) return void 0;
|
|
3547
|
+
return [
|
|
3548
|
+
codingKey3(coding),
|
|
3549
|
+
result.message ?? `${coding.code} is not in the list.`
|
|
3550
|
+
];
|
|
3551
|
+
} catch {
|
|
3552
|
+
return void 0;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
function useListCheck(service, binding, codings, storesCodes) {
|
|
3556
|
+
const [warnings, setWarnings] = useState11(NONE);
|
|
3557
|
+
const validate = useMemo6(() => service?.validate?.bind(service), [service]);
|
|
3558
|
+
const active = !!validate && checks(binding, storesCodes);
|
|
3559
|
+
const codes = active ? JSON.stringify(codings.map((c) => [c.system, c.code])) : "[]";
|
|
3560
|
+
const valueSet = binding?.valueSet;
|
|
3561
|
+
const system = binding?.system;
|
|
3562
|
+
const version = binding?.version;
|
|
3563
|
+
useEffect6(() => {
|
|
3564
|
+
const stored = JSON.parse(codes).map(
|
|
3565
|
+
([s, code]) => ({ system: s, code })
|
|
3566
|
+
);
|
|
3567
|
+
if (!validate || !active || stored.length === 0) {
|
|
3568
|
+
setWarnings(NONE);
|
|
3569
|
+
return;
|
|
3570
|
+
}
|
|
3571
|
+
const list = { valueSet, system, version, strength: "required" };
|
|
3572
|
+
let live = true;
|
|
3573
|
+
void Promise.all(stored.map((c) => warningOf(validate, list, c))).then(
|
|
3574
|
+
(found) => {
|
|
3575
|
+
if (!live) return;
|
|
3576
|
+
const entries = found.filter((w) => w !== void 0);
|
|
3577
|
+
setWarnings(entries.length ? new Map(entries) : NONE);
|
|
3578
|
+
}
|
|
3579
|
+
);
|
|
3580
|
+
return () => {
|
|
3581
|
+
live = false;
|
|
3582
|
+
};
|
|
3583
|
+
}, [active, validate, codes, valueSet, system, version]);
|
|
3584
|
+
return warnings;
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3587
|
+
// src/widgets/codedValue/HitList.tsx
|
|
3588
|
+
import { Check as Check3 } from "lucide-react";
|
|
3589
|
+
import { cn as cn11 } from "@petrarca/sonnet-core";
|
|
3590
|
+
import { codingKey as codingKey4 } from "@petrarca/sonnet-core/schema";
|
|
3591
|
+
import {
|
|
3592
|
+
CodedValueDisplay,
|
|
3593
|
+
Command as Command3,
|
|
3594
|
+
CommandEmpty as CommandEmpty3,
|
|
3595
|
+
CommandGroup as CommandGroup3,
|
|
3596
|
+
CommandInput as CommandInput3,
|
|
3597
|
+
CommandItem as CommandItem3,
|
|
3598
|
+
CommandList as CommandList3
|
|
3599
|
+
} from "@petrarca/sonnet-ui";
|
|
3600
|
+
import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3601
|
+
function emptyMessage(o) {
|
|
3602
|
+
if (o.loading) return "Loading...";
|
|
3603
|
+
if (o.error) return "The list could not be loaded.";
|
|
3604
|
+
const typed = o.search.trim().length;
|
|
3605
|
+
if (o.mode === "search" && typed < o.minChars)
|
|
3606
|
+
return o.minChars > 1 && typed > 0 ? `Type at least ${o.minChars} characters...` : "Type to search...";
|
|
3607
|
+
return "No matches.";
|
|
3608
|
+
}
|
|
3609
|
+
function HitItem({
|
|
3610
|
+
hit,
|
|
3611
|
+
selected,
|
|
3612
|
+
onPick
|
|
3613
|
+
}) {
|
|
3614
|
+
return /* @__PURE__ */ jsxs19(CommandItem3, { value: codingKey4(hit), onSelect: () => onPick(hit), children: [
|
|
3615
|
+
/* @__PURE__ */ jsx29(
|
|
3616
|
+
Check3,
|
|
3617
|
+
{
|
|
3618
|
+
className: cn11(
|
|
3619
|
+
"mr-2 h-4 w-4 shrink-0",
|
|
3620
|
+
selected ? "opacity-100" : "opacity-0"
|
|
3621
|
+
)
|
|
3622
|
+
}
|
|
3623
|
+
),
|
|
3624
|
+
/* @__PURE__ */ jsxs19("div", { className: "min-w-0", children: [
|
|
3625
|
+
/* @__PURE__ */ jsx29(CodedValueDisplay, { code: hit.code, display: hit.display }),
|
|
3626
|
+
hit.path && hit.path.length > 0 && /* @__PURE__ */ jsx29("div", { className: "truncate text-xs text-muted-foreground", children: hit.path.join(" \u203A ") })
|
|
3627
|
+
] })
|
|
3628
|
+
] });
|
|
3629
|
+
}
|
|
3630
|
+
function HitList({
|
|
3631
|
+
options,
|
|
3632
|
+
selected,
|
|
3633
|
+
freeText,
|
|
3634
|
+
onPick,
|
|
3635
|
+
onFreeText
|
|
3636
|
+
}) {
|
|
3637
|
+
return /* @__PURE__ */ jsxs19(Command3, { shouldFilter: false, children: [
|
|
3638
|
+
/* @__PURE__ */ jsx29(
|
|
3639
|
+
CommandInput3,
|
|
3640
|
+
{
|
|
3641
|
+
placeholder: "Search...",
|
|
3642
|
+
value: options.search,
|
|
3643
|
+
onValueChange: options.setSearch
|
|
3644
|
+
}
|
|
3645
|
+
),
|
|
3646
|
+
/* @__PURE__ */ jsxs19(CommandList3, { children: [
|
|
3647
|
+
/* @__PURE__ */ jsx29(CommandEmpty3, { children: emptyMessage(options) }),
|
|
3648
|
+
/* @__PURE__ */ jsxs19(CommandGroup3, { children: [
|
|
3649
|
+
options.hits.map((hit) => /* @__PURE__ */ jsx29(
|
|
3650
|
+
HitItem,
|
|
3651
|
+
{
|
|
3652
|
+
hit,
|
|
3653
|
+
selected: selected.has(codingKey4(hit)),
|
|
3654
|
+
onPick
|
|
3655
|
+
},
|
|
3656
|
+
codingKey4(hit)
|
|
3657
|
+
)),
|
|
3658
|
+
options.hasMore && /* @__PURE__ */ jsx29(CommandItem3, { value: "__more", onSelect: options.loadMore, children: /* @__PURE__ */ jsxs19("span", { className: "text-sm text-muted-foreground", children: [
|
|
3659
|
+
"Show more",
|
|
3660
|
+
options.total !== void 0 && ` (${options.hits.length} of ${options.total})`
|
|
3661
|
+
] }) }),
|
|
3662
|
+
freeText && /* @__PURE__ */ jsx29(CommandItem3, { value: "__text", onSelect: () => onFreeText(freeText), children: /* @__PURE__ */ jsxs19("span", { className: "text-sm", children: [
|
|
3663
|
+
"Use \u201C",
|
|
3664
|
+
freeText,
|
|
3665
|
+
"\u201D"
|
|
3666
|
+
] }) })
|
|
3667
|
+
] })
|
|
3668
|
+
] })
|
|
3669
|
+
] });
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
// src/widgets/codedValue/InlineIconButton.tsx
|
|
3673
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
3674
|
+
function InlineIconButton({
|
|
3675
|
+
label,
|
|
3676
|
+
onAct,
|
|
3677
|
+
children
|
|
3678
|
+
}) {
|
|
3679
|
+
const act = (e) => {
|
|
3680
|
+
e.preventDefault();
|
|
3681
|
+
e.stopPropagation();
|
|
3682
|
+
onAct();
|
|
3683
|
+
};
|
|
3684
|
+
return /* @__PURE__ */ jsx30(
|
|
3685
|
+
"span",
|
|
3686
|
+
{
|
|
3687
|
+
role: "button",
|
|
3688
|
+
"aria-label": label,
|
|
3689
|
+
tabIndex: -1,
|
|
3690
|
+
className: "rounded-sm opacity-50 hover:opacity-100",
|
|
3691
|
+
onPointerDown: act,
|
|
3692
|
+
children
|
|
3693
|
+
}
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
// src/widgets/codedValue/listOptions.ts
|
|
3698
|
+
import * as React12 from "react";
|
|
3699
|
+
function modeOption(options) {
|
|
3700
|
+
const mode = options?.mode;
|
|
3701
|
+
return mode === "list" || mode === "search" ? mode : void 0;
|
|
3702
|
+
}
|
|
3703
|
+
function minCharsOption(options) {
|
|
3704
|
+
const n = options?.minChars;
|
|
3705
|
+
return typeof n === "number" && n >= 1 ? n : 1;
|
|
3706
|
+
}
|
|
3707
|
+
function useListRef(binding) {
|
|
3708
|
+
const { valueSet, system, version } = binding ?? {};
|
|
3709
|
+
return React12.useMemo(
|
|
3710
|
+
() => valueSet || system ? { valueSet, system, version } : void 0,
|
|
3711
|
+
[valueSet, system, version]
|
|
3712
|
+
);
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3715
|
+
// src/widgets/CodedValueWidget.tsx
|
|
3716
|
+
import { jsx as jsx31, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
3717
|
+
function freeTextOf(o, open) {
|
|
3718
|
+
const text = o.search.trim();
|
|
3719
|
+
if (!open || !text) return void 0;
|
|
3720
|
+
return o.hits.some((h) => h.code === text) ? void 0 : text;
|
|
3721
|
+
}
|
|
3722
|
+
function EntryText({
|
|
3723
|
+
entry,
|
|
3724
|
+
shown
|
|
3725
|
+
}) {
|
|
3726
|
+
const key = entryKey(entry);
|
|
3727
|
+
const known = shown.picked.has(key);
|
|
3728
|
+
const looked = useStoredLabel(
|
|
3729
|
+
known || !shown.lookup ? null : shown.service,
|
|
3730
|
+
entry.coding,
|
|
3731
|
+
shown.binding
|
|
3732
|
+
);
|
|
3733
|
+
if (!entry.coding)
|
|
3734
|
+
return /* @__PURE__ */ jsx31("span", { className: "truncate text-sm", children: entry.text });
|
|
3735
|
+
return /* @__PURE__ */ jsx31(
|
|
3736
|
+
CodedValueDisplay2,
|
|
3737
|
+
{
|
|
3738
|
+
code: entry.coding.code,
|
|
3739
|
+
display: known ? shown.picked.get(key) : looked,
|
|
3740
|
+
system: entry.coding.system || void 0
|
|
3741
|
+
}
|
|
3742
|
+
);
|
|
3743
|
+
}
|
|
3744
|
+
function Chip({
|
|
3745
|
+
entry,
|
|
3746
|
+
shown,
|
|
3747
|
+
onRemove
|
|
3748
|
+
}) {
|
|
3749
|
+
return /* @__PURE__ */ jsxs20(Badge4, { variant: "secondary", className: "max-w-full gap-1 font-normal", children: [
|
|
3750
|
+
/* @__PURE__ */ jsx31(EntryText, { entry, shown }),
|
|
3751
|
+
onRemove && /* @__PURE__ */ jsx31(
|
|
3752
|
+
InlineIconButton,
|
|
3753
|
+
{
|
|
3754
|
+
label: "Remove",
|
|
3755
|
+
onAct: () => onRemove(entryKey(entry)),
|
|
3756
|
+
children: /* @__PURE__ */ jsx31(X5, { className: "h-3 w-3" })
|
|
3757
|
+
}
|
|
3758
|
+
)
|
|
3759
|
+
] });
|
|
3760
|
+
}
|
|
3761
|
+
function Entries({
|
|
3762
|
+
entries,
|
|
3763
|
+
field,
|
|
3764
|
+
shown,
|
|
3765
|
+
onRemove
|
|
3766
|
+
}) {
|
|
3767
|
+
if (!field.multiple) return /* @__PURE__ */ jsx31(EntryText, { entry: entries[0], shown });
|
|
3768
|
+
return /* @__PURE__ */ jsx31("span", { className: "flex min-w-0 flex-wrap gap-1", children: entries.map((e) => /* @__PURE__ */ jsx31(Chip, { entry: e, shown, onRemove }, entryKey(e))) });
|
|
3769
|
+
}
|
|
3770
|
+
function TriggerIcons3({
|
|
3771
|
+
loading,
|
|
3772
|
+
onClear
|
|
3773
|
+
}) {
|
|
3774
|
+
return /* @__PURE__ */ jsxs20("span", { className: "ml-2 flex shrink-0 items-center gap-1", children: [
|
|
3775
|
+
loading && /* @__PURE__ */ jsx31(Loader2, { className: "h-4 w-4 animate-spin opacity-50" }),
|
|
3776
|
+
onClear && /* @__PURE__ */ jsx31(InlineIconButton, { label: "Clear selection", onAct: onClear, children: /* @__PURE__ */ jsx31(X5, { className: "h-4 w-4" }) }),
|
|
3777
|
+
/* @__PURE__ */ jsx31(ChevronsUpDown4, { className: "h-4 w-4 opacity-50" })
|
|
3778
|
+
] });
|
|
3779
|
+
}
|
|
3780
|
+
function fieldStateOf(props) {
|
|
3781
|
+
const binding = bindingOf(props.propertySchema);
|
|
3782
|
+
const field = fieldModelOf(props.propertySchema, props.options);
|
|
3783
|
+
return {
|
|
3784
|
+
binding,
|
|
3785
|
+
field,
|
|
3786
|
+
entries: entriesOf(props.value, field, binding),
|
|
3787
|
+
storesCodes: field.store === "code"
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
function useAnswers(props, state) {
|
|
3791
|
+
const { value, onChange } = props;
|
|
3792
|
+
const { binding, field } = state;
|
|
3793
|
+
const [picked, setPicked] = React13.useState(
|
|
3794
|
+
() => /* @__PURE__ */ new Map()
|
|
3795
|
+
);
|
|
3796
|
+
const pick = (hit) => {
|
|
3797
|
+
setPicked((m) => new Map(m).set(codingKey5(hit), hit.display));
|
|
3798
|
+
onChange(withAnswer(value, toStored(hit, field, binding), field, binding));
|
|
3799
|
+
};
|
|
3800
|
+
const takeText = (typed) => onChange(withAnswer(value, typed, field, binding));
|
|
3801
|
+
const remove = (key) => onChange(withoutAnswer(value, key, field, binding));
|
|
3802
|
+
return { picked, pick, takeText, remove };
|
|
3803
|
+
}
|
|
3804
|
+
function EditableCodedValue({
|
|
3805
|
+
props,
|
|
3806
|
+
state,
|
|
3807
|
+
service
|
|
3808
|
+
}) {
|
|
3809
|
+
const { id, value, onChange, onBlur, required, rawErrors, placeholder } = props;
|
|
3810
|
+
const { binding, field, entries } = state;
|
|
3811
|
+
const [open, setOpen] = React13.useState(false);
|
|
3812
|
+
const list = useListRef(binding);
|
|
3813
|
+
const options = useCodedValueOptions(
|
|
3814
|
+
service,
|
|
3815
|
+
list,
|
|
3816
|
+
modeOption(props.options),
|
|
3817
|
+
minCharsOption(props.options)
|
|
3818
|
+
);
|
|
3819
|
+
const answers = useAnswers(props, state);
|
|
3820
|
+
const shown = {
|
|
3821
|
+
binding,
|
|
3822
|
+
service,
|
|
3823
|
+
picked: answers.picked,
|
|
3824
|
+
lookup: state.storesCodes
|
|
3825
|
+
};
|
|
3826
|
+
const empty = entries.length === 0;
|
|
3827
|
+
const close = (next) => {
|
|
3828
|
+
setOpen(next);
|
|
3829
|
+
if (!next) {
|
|
3830
|
+
options.setSearch("");
|
|
3831
|
+
onBlur?.(id, value);
|
|
3832
|
+
}
|
|
3833
|
+
};
|
|
3834
|
+
const done = () => {
|
|
3835
|
+
if (!field.multiple) close(false);
|
|
3836
|
+
};
|
|
3837
|
+
const clear = () => onChange(void 0);
|
|
3838
|
+
return /* @__PURE__ */ jsxs20(Popover3, { open, onOpenChange: close, children: [
|
|
3839
|
+
/* @__PURE__ */ jsx31(PopoverTrigger3, { asChild: true, children: /* @__PURE__ */ jsxs20(
|
|
3840
|
+
Button7,
|
|
3841
|
+
{
|
|
3842
|
+
id,
|
|
3843
|
+
variant: "outline",
|
|
3844
|
+
role: "combobox",
|
|
3845
|
+
"aria-expanded": open,
|
|
3846
|
+
className: cn12(
|
|
3847
|
+
"h-auto min-h-9 w-full justify-between font-normal",
|
|
3848
|
+
empty && "text-muted-foreground",
|
|
3849
|
+
firstError(rawErrors) && "border-destructive"
|
|
3850
|
+
),
|
|
3851
|
+
children: [
|
|
3852
|
+
empty ? /* @__PURE__ */ jsx31("span", { className: "truncate", children: placeholder || "Select..." }) : /* @__PURE__ */ jsx31(
|
|
3853
|
+
Entries,
|
|
3854
|
+
{
|
|
3855
|
+
entries,
|
|
3856
|
+
field,
|
|
3857
|
+
shown,
|
|
3858
|
+
onRemove: answers.remove
|
|
3859
|
+
}
|
|
3860
|
+
),
|
|
3861
|
+
/* @__PURE__ */ jsx31(
|
|
3862
|
+
TriggerIcons3,
|
|
3863
|
+
{
|
|
3864
|
+
loading: options.loading,
|
|
3865
|
+
onClear: !empty && !required ? clear : void 0
|
|
3866
|
+
}
|
|
3867
|
+
)
|
|
3868
|
+
]
|
|
3869
|
+
}
|
|
3870
|
+
) }),
|
|
3871
|
+
/* @__PURE__ */ jsx31(
|
|
3872
|
+
PopoverContent3,
|
|
3873
|
+
{
|
|
3874
|
+
className: "w-[--radix-popover-trigger-width] p-0",
|
|
3875
|
+
align: "start",
|
|
3876
|
+
children: /* @__PURE__ */ jsx31(
|
|
3877
|
+
HitList,
|
|
3878
|
+
{
|
|
3879
|
+
options,
|
|
3880
|
+
selected: new Set(entries.map(entryKey)),
|
|
3881
|
+
freeText: freeTextOf(options, takesText(field, binding)),
|
|
3882
|
+
onPick: (hit) => {
|
|
3883
|
+
answers.pick(hit);
|
|
3884
|
+
done();
|
|
3885
|
+
},
|
|
3886
|
+
onFreeText: (typed) => {
|
|
3887
|
+
answers.takeText(typed);
|
|
3888
|
+
done();
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
)
|
|
3892
|
+
}
|
|
3893
|
+
)
|
|
3894
|
+
] });
|
|
3895
|
+
}
|
|
3896
|
+
function ReadonlyCodedValue({
|
|
3897
|
+
state,
|
|
3898
|
+
service,
|
|
3899
|
+
note
|
|
3900
|
+
}) {
|
|
3901
|
+
const shown = {
|
|
3902
|
+
binding: state.binding,
|
|
3903
|
+
service,
|
|
3904
|
+
picked: /* @__PURE__ */ new Map(),
|
|
3905
|
+
lookup: state.storesCodes
|
|
3906
|
+
};
|
|
3907
|
+
return /* @__PURE__ */ jsxs20("div", { className: "space-y-1", children: [
|
|
3908
|
+
state.entries.length ? /* @__PURE__ */ jsx31(Entries, { entries: state.entries, field: state.field, shown }) : /* @__PURE__ */ jsx31("span", { className: "text-sm text-muted-foreground", children: EMPTY_VALUE }),
|
|
3909
|
+
note && /* @__PURE__ */ jsx31("p", { className: "text-xs text-muted-foreground", children: note })
|
|
3910
|
+
] });
|
|
3911
|
+
}
|
|
3912
|
+
function readonlyNote(state, service) {
|
|
3913
|
+
if (!state.binding) return "No list is bound to this field.";
|
|
3914
|
+
if (!service) return "No terminology service is available.";
|
|
3915
|
+
return void 0;
|
|
3916
|
+
}
|
|
3917
|
+
function ListWarningsText({
|
|
3918
|
+
warnings
|
|
3919
|
+
}) {
|
|
3920
|
+
if (warnings.size === 0) return null;
|
|
3921
|
+
return /* @__PURE__ */ jsx31("div", { className: "space-y-0.5", children: [...warnings].map(([key, message]) => /* @__PURE__ */ jsxs20(
|
|
3922
|
+
"p",
|
|
3923
|
+
{
|
|
3924
|
+
className: "flex items-center gap-1 text-xs text-amber-600 dark:text-amber-400",
|
|
3925
|
+
children: [
|
|
3926
|
+
/* @__PURE__ */ jsx31(TriangleAlert, { className: "h-3 w-3 shrink-0" }),
|
|
3927
|
+
message
|
|
3928
|
+
]
|
|
3929
|
+
},
|
|
3930
|
+
key
|
|
3931
|
+
)) });
|
|
3932
|
+
}
|
|
3933
|
+
function CodedValueBody({
|
|
3934
|
+
props,
|
|
3935
|
+
state,
|
|
3936
|
+
service
|
|
3937
|
+
}) {
|
|
3938
|
+
const note = readonlyNote(state, service);
|
|
3939
|
+
if (props.disabled || props.readonly || note || !service)
|
|
3940
|
+
return /* @__PURE__ */ jsx31(
|
|
3941
|
+
ReadonlyCodedValue,
|
|
3942
|
+
{
|
|
3943
|
+
state,
|
|
3944
|
+
service,
|
|
3945
|
+
note: props.disabled || props.readonly ? void 0 : note
|
|
3946
|
+
}
|
|
3947
|
+
);
|
|
3948
|
+
return /* @__PURE__ */ jsx31(EditableCodedValue, { props, state, service });
|
|
3949
|
+
}
|
|
3950
|
+
function CodedValueField({
|
|
3951
|
+
props
|
|
3952
|
+
}) {
|
|
3953
|
+
const service = useTerminology();
|
|
3954
|
+
const state = fieldStateOf(props);
|
|
3955
|
+
const warnings = useListCheck(
|
|
3956
|
+
service,
|
|
3957
|
+
state.binding,
|
|
3958
|
+
state.entries.flatMap((e) => e.coding ? [e.coding] : []),
|
|
3959
|
+
state.storesCodes
|
|
3960
|
+
);
|
|
3961
|
+
return /* @__PURE__ */ jsxs20("div", { className: "space-y-1", children: [
|
|
3962
|
+
/* @__PURE__ */ jsx31(CodedValueBody, { props, state, service }),
|
|
3963
|
+
/* @__PURE__ */ jsx31(ListWarningsText, { warnings })
|
|
3964
|
+
] });
|
|
3965
|
+
}
|
|
3966
|
+
function CodedValueWidget(props) {
|
|
3967
|
+
return /* @__PURE__ */ jsx31(
|
|
3968
|
+
FormFieldWrapper,
|
|
3969
|
+
{
|
|
3970
|
+
inputId: props.id,
|
|
3971
|
+
label: props.label,
|
|
3972
|
+
description: props.description,
|
|
3973
|
+
required: props.required,
|
|
3974
|
+
error: firstError(props.rawErrors),
|
|
3975
|
+
children: /* @__PURE__ */ jsx31(CodedValueField, { props })
|
|
3976
|
+
}
|
|
3977
|
+
);
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
// src/widgets/CodeableConceptWidget.tsx
|
|
3981
|
+
import * as React14 from "react";
|
|
3982
|
+
import { Plus as Plus4, X as X6 } from "lucide-react";
|
|
3983
|
+
import { cn as cn13 } from "@petrarca/sonnet-core";
|
|
3984
|
+
import {
|
|
3985
|
+
bindingLists,
|
|
3986
|
+
bindingOf as bindingOf2,
|
|
3987
|
+
CODING_TYPE,
|
|
3988
|
+
codingKey as codingKey6,
|
|
3989
|
+
codingsByList as codingsByList2,
|
|
3990
|
+
conceptOf as conceptOf2,
|
|
3991
|
+
listTitle
|
|
3992
|
+
} from "@petrarca/sonnet-core/schema";
|
|
3993
|
+
import {
|
|
3994
|
+
useTerminology as useTerminology2
|
|
3995
|
+
} from "@petrarca/sonnet-core/terminology";
|
|
3996
|
+
import {
|
|
3997
|
+
Badge as Badge5,
|
|
3998
|
+
Button as Button8,
|
|
3999
|
+
DropdownMenu,
|
|
4000
|
+
DropdownMenuContent,
|
|
4001
|
+
DropdownMenuItem,
|
|
4002
|
+
DropdownMenuTrigger,
|
|
4003
|
+
Popover as Popover4,
|
|
4004
|
+
PopoverAnchor,
|
|
4005
|
+
PopoverContent as PopoverContent4
|
|
4006
|
+
} from "@petrarca/sonnet-ui";
|
|
4007
|
+
|
|
4008
|
+
// src/widgets/codeableConcept/conceptModel.ts
|
|
4009
|
+
import {
|
|
4010
|
+
codingsByList,
|
|
4011
|
+
conceptOf
|
|
4012
|
+
} from "@petrarca/sonnet-core/schema";
|
|
4013
|
+
function valueOf(codings, text) {
|
|
4014
|
+
const t = text?.trim() ? text : void 0;
|
|
4015
|
+
if (codings.length === 0 && !t) return void 0;
|
|
4016
|
+
return {
|
|
4017
|
+
...codings.length ? { codings } : {},
|
|
4018
|
+
...t ? { text: t } : {}
|
|
4019
|
+
};
|
|
4020
|
+
}
|
|
4021
|
+
function withRow(value, lists, row, coding) {
|
|
4022
|
+
return withCoding(
|
|
4023
|
+
value,
|
|
4024
|
+
lists,
|
|
4025
|
+
row,
|
|
4026
|
+
coding && { ...coding, userSelected: true }
|
|
4027
|
+
);
|
|
4028
|
+
}
|
|
4029
|
+
function withSuggested(value, lists, row, suggestion) {
|
|
4030
|
+
if (codingsByList(value, lists).byList[row]) return conceptOf(value);
|
|
4031
|
+
const { coding, match } = suggestion;
|
|
4032
|
+
return withCoding(value, lists, row, {
|
|
4033
|
+
...coding,
|
|
4034
|
+
...match ? { match } : {}
|
|
4035
|
+
});
|
|
4036
|
+
}
|
|
4037
|
+
function withCoding(value, lists, row, coding) {
|
|
4038
|
+
const { byList: rows, others } = codingsByList(value, lists);
|
|
4039
|
+
const purpose = lists[row]?.purpose;
|
|
4040
|
+
const next = rows.map(
|
|
4041
|
+
(c, i) => i !== row ? c : coding && { ...coding, ...purpose ? { purpose } : {} }
|
|
4042
|
+
);
|
|
4043
|
+
return valueOf(
|
|
4044
|
+
[...next.filter((c) => !!c), ...others],
|
|
4045
|
+
conceptOf(value).text
|
|
4046
|
+
);
|
|
4047
|
+
}
|
|
4048
|
+
function withText(value, text) {
|
|
4049
|
+
return valueOf(conceptOf(value).codings ?? [], text);
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
// src/widgets/CodeableConceptWidget.tsx
|
|
4053
|
+
import { jsx as jsx32, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
4054
|
+
function listSchema(list) {
|
|
4055
|
+
const { valueSet, system, version } = list;
|
|
4056
|
+
return {
|
|
4057
|
+
...CODING_TYPE.definition,
|
|
4058
|
+
"x-binding": {
|
|
4059
|
+
...valueSet ? { valueSet } : {},
|
|
4060
|
+
...system ? { system } : {},
|
|
4061
|
+
...version ? { version } : {},
|
|
4062
|
+
strength: "required"
|
|
4063
|
+
}
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
4066
|
+
var AS_CODING = {
|
|
4067
|
+
multiple: false,
|
|
4068
|
+
shape: "coding",
|
|
4069
|
+
store: "code",
|
|
4070
|
+
keep: []
|
|
4071
|
+
};
|
|
4072
|
+
function offersText(props, extensible) {
|
|
4073
|
+
const text = props.options?.text;
|
|
4074
|
+
return typeof text === "boolean" ? text : extensible;
|
|
4075
|
+
}
|
|
4076
|
+
function CodingChip({
|
|
4077
|
+
label,
|
|
4078
|
+
coding,
|
|
4079
|
+
onOpen,
|
|
4080
|
+
onRemove
|
|
4081
|
+
}) {
|
|
4082
|
+
const mapped = coding.match !== void 0 && !coding.userSelected;
|
|
4083
|
+
return /* @__PURE__ */ jsxs21(
|
|
4084
|
+
Badge5,
|
|
4085
|
+
{
|
|
4086
|
+
variant: "secondary",
|
|
4087
|
+
className: cn13("gap-1 font-normal", onOpen && "cursor-pointer"),
|
|
4088
|
+
title: coding.display ?? coding.code,
|
|
4089
|
+
onClick: onOpen,
|
|
4090
|
+
children: [
|
|
4091
|
+
/* @__PURE__ */ jsx32("span", { className: "text-muted-foreground", children: label }),
|
|
4092
|
+
/* @__PURE__ */ jsx32("span", { className: "font-mono text-xs", children: coding.code }),
|
|
4093
|
+
mapped && /* @__PURE__ */ jsx32("span", { className: "text-muted-foreground", children: "\xB7 mapped" }),
|
|
4094
|
+
onRemove && /* @__PURE__ */ jsx32(InlineIconButton, { label: `Remove ${label}`, onAct: onRemove, children: /* @__PURE__ */ jsx32(X6, { className: "h-3 w-3" }) })
|
|
4095
|
+
]
|
|
4096
|
+
}
|
|
4097
|
+
);
|
|
4098
|
+
}
|
|
4099
|
+
function MissingChip({
|
|
4100
|
+
label,
|
|
4101
|
+
onOpen
|
|
4102
|
+
}) {
|
|
4103
|
+
return /* @__PURE__ */ jsxs21(
|
|
4104
|
+
Badge5,
|
|
4105
|
+
{
|
|
4106
|
+
variant: "outline",
|
|
4107
|
+
className: cn13(
|
|
4108
|
+
"gap-1 border-dashed font-normal text-muted-foreground",
|
|
4109
|
+
onOpen && "cursor-pointer"
|
|
4110
|
+
),
|
|
4111
|
+
onClick: onOpen,
|
|
4112
|
+
children: [
|
|
4113
|
+
label,
|
|
4114
|
+
/* @__PURE__ */ jsx32("span", { className: "text-destructive", children: "*" })
|
|
4115
|
+
]
|
|
4116
|
+
}
|
|
4117
|
+
);
|
|
4118
|
+
}
|
|
4119
|
+
function ListSearch({
|
|
4120
|
+
list,
|
|
4121
|
+
props,
|
|
4122
|
+
selected,
|
|
4123
|
+
onPick
|
|
4124
|
+
}) {
|
|
4125
|
+
const service = useTerminology2();
|
|
4126
|
+
const ref = useListRef({ ...list, strength: "required" });
|
|
4127
|
+
const options = useCodedValueOptions(
|
|
4128
|
+
service,
|
|
4129
|
+
ref,
|
|
4130
|
+
modeOption(props.options),
|
|
4131
|
+
minCharsOption(props.options)
|
|
4132
|
+
);
|
|
4133
|
+
return /* @__PURE__ */ jsxs21("div", { className: "space-y-1", children: [
|
|
4134
|
+
/* @__PURE__ */ jsx32("p", { className: "px-2 pt-2 text-xs font-medium text-muted-foreground", children: listTitle(list) }),
|
|
4135
|
+
/* @__PURE__ */ jsx32(
|
|
4136
|
+
HitList,
|
|
4137
|
+
{
|
|
4138
|
+
options,
|
|
4139
|
+
selected: new Set(selected ? [codingKey6(selected)] : []),
|
|
4140
|
+
onPick,
|
|
4141
|
+
onFreeText: () => void 0
|
|
4142
|
+
}
|
|
4143
|
+
)
|
|
4144
|
+
] });
|
|
4145
|
+
}
|
|
4146
|
+
function AddCoding({
|
|
4147
|
+
lists,
|
|
4148
|
+
onOpen
|
|
4149
|
+
}) {
|
|
4150
|
+
const chosen = React14.useRef(null);
|
|
4151
|
+
if (lists.length === 0) return null;
|
|
4152
|
+
return /* @__PURE__ */ jsxs21(DropdownMenu, { children: [
|
|
4153
|
+
/* @__PURE__ */ jsx32(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs21(
|
|
4154
|
+
Button8,
|
|
4155
|
+
{
|
|
4156
|
+
type: "button",
|
|
4157
|
+
variant: "ghost",
|
|
4158
|
+
size: "sm",
|
|
4159
|
+
className: "h-6 gap-1 px-2 text-xs",
|
|
4160
|
+
children: [
|
|
4161
|
+
/* @__PURE__ */ jsx32(Plus4, { className: "h-3 w-3" }),
|
|
4162
|
+
"Coding"
|
|
4163
|
+
]
|
|
4164
|
+
}
|
|
4165
|
+
) }),
|
|
4166
|
+
/* @__PURE__ */ jsx32(
|
|
4167
|
+
DropdownMenuContent,
|
|
4168
|
+
{
|
|
4169
|
+
align: "start",
|
|
4170
|
+
onCloseAutoFocus: (e) => {
|
|
4171
|
+
if (chosen.current === null) return;
|
|
4172
|
+
e.preventDefault();
|
|
4173
|
+
onOpen(chosen.current);
|
|
4174
|
+
chosen.current = null;
|
|
4175
|
+
},
|
|
4176
|
+
children: lists.map(([list, row]) => /* @__PURE__ */ jsx32(DropdownMenuItem, { onSelect: () => chosen.current = row, children: listTitle(list) }, row))
|
|
4177
|
+
}
|
|
4178
|
+
)
|
|
4179
|
+
] });
|
|
4180
|
+
}
|
|
4181
|
+
function ListChip({
|
|
4182
|
+
list,
|
|
4183
|
+
row,
|
|
4184
|
+
coding,
|
|
4185
|
+
open,
|
|
4186
|
+
set
|
|
4187
|
+
}) {
|
|
4188
|
+
const onOpen = open && (() => open(row));
|
|
4189
|
+
if (coding)
|
|
4190
|
+
return /* @__PURE__ */ jsx32(
|
|
4191
|
+
CodingChip,
|
|
4192
|
+
{
|
|
4193
|
+
label: listTitle(list),
|
|
4194
|
+
coding,
|
|
4195
|
+
onOpen,
|
|
4196
|
+
onRemove: set && (() => set(row, void 0))
|
|
4197
|
+
}
|
|
4198
|
+
);
|
|
4199
|
+
return list.required ? /* @__PURE__ */ jsx32(MissingChip, { label: listTitle(list), onOpen }) : null;
|
|
4200
|
+
}
|
|
4201
|
+
function ChipLine({ lists, rows, others, open, set, addText }) {
|
|
4202
|
+
const addable = lists.map((l, i) => [l, i]).filter(([l, i]) => i > 0 && !rows[i] && !l.required);
|
|
4203
|
+
return /* @__PURE__ */ jsxs21("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
4204
|
+
lists.slice(1).map((list, k) => /* @__PURE__ */ jsx32(
|
|
4205
|
+
ListChip,
|
|
4206
|
+
{
|
|
4207
|
+
list,
|
|
4208
|
+
row: k + 1,
|
|
4209
|
+
coding: rows[k + 1],
|
|
4210
|
+
open,
|
|
4211
|
+
set
|
|
4212
|
+
},
|
|
4213
|
+
k + 1
|
|
4214
|
+
)),
|
|
4215
|
+
others.map((c) => /* @__PURE__ */ jsx32(CodingChip, { label: listTitle(c), coding: c }, codingKey6(c))),
|
|
4216
|
+
open && /* @__PURE__ */ jsx32(AddCoding, { lists: addable, onOpen: open }),
|
|
4217
|
+
addText && /* @__PURE__ */ jsxs21(
|
|
4218
|
+
Button8,
|
|
4219
|
+
{
|
|
4220
|
+
type: "button",
|
|
4221
|
+
variant: "ghost",
|
|
4222
|
+
size: "sm",
|
|
4223
|
+
className: "h-6 gap-1 px-2 text-xs",
|
|
4224
|
+
onClick: addText,
|
|
4225
|
+
children: [
|
|
4226
|
+
/* @__PURE__ */ jsx32(Plus4, { className: "h-3 w-3" }),
|
|
4227
|
+
"Text"
|
|
4228
|
+
]
|
|
4229
|
+
}
|
|
4230
|
+
)
|
|
4231
|
+
] });
|
|
4232
|
+
}
|
|
4233
|
+
function useConcept(props) {
|
|
4234
|
+
const binding = bindingOf2(props.propertySchema);
|
|
4235
|
+
const lists = binding ? bindingLists(binding) : [];
|
|
4236
|
+
const text = conceptOf2(props.value).text ?? "";
|
|
4237
|
+
const [editing, setEditing] = React14.useState(null);
|
|
4238
|
+
const [showText, setShowText] = React14.useState(false);
|
|
4239
|
+
const offered = offersText(props, binding?.strength === "extensible");
|
|
4240
|
+
const { byList: rows, others } = codingsByList2(props.value, lists);
|
|
4241
|
+
return {
|
|
4242
|
+
lists,
|
|
4243
|
+
rows,
|
|
4244
|
+
others,
|
|
4245
|
+
text,
|
|
4246
|
+
editing,
|
|
4247
|
+
setEditing,
|
|
4248
|
+
textShown: offered && (showText || text !== ""),
|
|
4249
|
+
textAsked: showText,
|
|
4250
|
+
addText: offered && !showText && text === "" ? () => setShowText(true) : void 0
|
|
4251
|
+
};
|
|
4252
|
+
}
|
|
4253
|
+
function useLatestValue(props) {
|
|
4254
|
+
const latest = React14.useRef(props.value);
|
|
4255
|
+
latest.current = props.value;
|
|
4256
|
+
return {
|
|
4257
|
+
get: () => latest.current,
|
|
4258
|
+
change: (next) => {
|
|
4259
|
+
latest.current = next;
|
|
4260
|
+
props.onChange(next);
|
|
4261
|
+
}
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
function useSuggestions(lists, { get, change }) {
|
|
4265
|
+
const service = useTerminology2();
|
|
4266
|
+
return (coding) => {
|
|
4267
|
+
if (!coding || !service?.translate) return;
|
|
4268
|
+
const { system, code, version } = coding;
|
|
4269
|
+
const empty = codingsByList2(get(), lists).byList.flatMap(
|
|
4270
|
+
(c, row) => row > 0 && !c ? [row] : []
|
|
4271
|
+
);
|
|
4272
|
+
for (const row of empty)
|
|
4273
|
+
service.translate({
|
|
4274
|
+
coding: { system, code, ...version ? { version } : {} },
|
|
4275
|
+
targets: [lists[row]]
|
|
4276
|
+
}).then(([first]) => {
|
|
4277
|
+
if (first) change(withSuggested(get(), lists, row, first));
|
|
4278
|
+
}).catch(() => void 0);
|
|
4279
|
+
};
|
|
4280
|
+
}
|
|
4281
|
+
function CodeableConceptWidget(props) {
|
|
4282
|
+
const { id, value, onBlur } = props;
|
|
4283
|
+
const c = useConcept(props);
|
|
4284
|
+
const editable = !props.disabled && !props.readonly;
|
|
4285
|
+
const blur = () => onBlur?.(id, value);
|
|
4286
|
+
const latest = useLatestValue(props);
|
|
4287
|
+
const suggest = useSuggestions(c.lists, latest);
|
|
4288
|
+
const set = (row, coding) => {
|
|
4289
|
+
latest.change(withRow(latest.get(), c.lists, row, coding));
|
|
4290
|
+
if (row === 0) suggest(coding);
|
|
4291
|
+
};
|
|
4292
|
+
const editingList = c.editing !== null ? c.lists[c.editing] : void 0;
|
|
4293
|
+
return /* @__PURE__ */ jsx32(
|
|
4294
|
+
FormFieldWrapper,
|
|
4295
|
+
{
|
|
4296
|
+
inputId: id,
|
|
4297
|
+
label: props.label,
|
|
4298
|
+
description: props.description,
|
|
4299
|
+
required: props.required,
|
|
4300
|
+
error: firstError(props.rawErrors),
|
|
4301
|
+
children: /* @__PURE__ */ jsxs21("div", { className: "space-y-1.5", children: [
|
|
4302
|
+
/* @__PURE__ */ jsx32(
|
|
4303
|
+
FirstList,
|
|
4304
|
+
{
|
|
4305
|
+
props,
|
|
4306
|
+
lists: c.lists,
|
|
4307
|
+
coding: c.rows[0],
|
|
4308
|
+
set,
|
|
4309
|
+
blur
|
|
4310
|
+
}
|
|
4311
|
+
),
|
|
4312
|
+
/* @__PURE__ */ jsxs21(
|
|
4313
|
+
Popover4,
|
|
4314
|
+
{
|
|
4315
|
+
open: editingList !== void 0,
|
|
4316
|
+
onOpenChange: (o) => {
|
|
4317
|
+
if (o) return;
|
|
4318
|
+
c.setEditing(null);
|
|
4319
|
+
blur();
|
|
4320
|
+
},
|
|
4321
|
+
children: [
|
|
4322
|
+
/* @__PURE__ */ jsx32(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx32("div", { children: /* @__PURE__ */ jsx32(
|
|
4323
|
+
ChipLine,
|
|
4324
|
+
{
|
|
4325
|
+
lists: c.lists,
|
|
4326
|
+
rows: c.rows,
|
|
4327
|
+
others: c.others,
|
|
4328
|
+
open: editable ? c.setEditing : void 0,
|
|
4329
|
+
set: editable ? set : void 0,
|
|
4330
|
+
addText: editable ? c.addText : void 0
|
|
4331
|
+
}
|
|
4332
|
+
) }) }),
|
|
4333
|
+
/* @__PURE__ */ jsx32(
|
|
4334
|
+
PopoverContent4,
|
|
4335
|
+
{
|
|
4336
|
+
className: "w-[--radix-popover-trigger-width] min-w-72 p-0",
|
|
4337
|
+
align: "start",
|
|
4338
|
+
children: editingList && c.editing !== null && /* @__PURE__ */ jsx32(
|
|
4339
|
+
ListSearch,
|
|
4340
|
+
{
|
|
4341
|
+
list: editingList,
|
|
4342
|
+
props,
|
|
4343
|
+
selected: c.rows[c.editing],
|
|
4344
|
+
onPick: (hit) => {
|
|
4345
|
+
set(
|
|
4346
|
+
c.editing,
|
|
4347
|
+
toStored(hit, AS_CODING, void 0)
|
|
4348
|
+
);
|
|
4349
|
+
c.setEditing(null);
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
)
|
|
4353
|
+
}
|
|
4354
|
+
)
|
|
4355
|
+
]
|
|
4356
|
+
}
|
|
4357
|
+
),
|
|
4358
|
+
c.textShown && /* @__PURE__ */ jsx32(
|
|
4359
|
+
FormInput,
|
|
4360
|
+
{
|
|
4361
|
+
id: `${id}-text`,
|
|
4362
|
+
label: "Text",
|
|
4363
|
+
value: c.text,
|
|
4364
|
+
placeholder: "In words, as the person said it",
|
|
4365
|
+
disabled: props.disabled,
|
|
4366
|
+
readOnly: props.readonly,
|
|
4367
|
+
onChange: (e) => latest.change(withText(latest.get(), e.target.value)),
|
|
4368
|
+
onBlur: blur,
|
|
4369
|
+
autoFocus: c.textAsked
|
|
4370
|
+
}
|
|
4371
|
+
)
|
|
4372
|
+
] })
|
|
4373
|
+
}
|
|
4374
|
+
);
|
|
4375
|
+
}
|
|
4376
|
+
function FirstList({
|
|
4377
|
+
props,
|
|
4378
|
+
lists,
|
|
4379
|
+
coding,
|
|
4380
|
+
set,
|
|
4381
|
+
blur
|
|
4382
|
+
}) {
|
|
4383
|
+
if (!lists[0])
|
|
4384
|
+
return /* @__PURE__ */ jsx32("p", { className: "text-sm text-muted-foreground", children: "No list is bound to this field." });
|
|
4385
|
+
return /* @__PURE__ */ jsx32(
|
|
4386
|
+
CodedValueWidget,
|
|
4387
|
+
{
|
|
4388
|
+
...props,
|
|
4389
|
+
label: "",
|
|
4390
|
+
description: void 0,
|
|
4391
|
+
rawErrors: void 0,
|
|
4392
|
+
propertySchema: listSchema(lists[0]),
|
|
4393
|
+
value: coding,
|
|
4394
|
+
onChange: (next) => set(0, next),
|
|
4395
|
+
onBlur: blur
|
|
2705
4396
|
}
|
|
2706
4397
|
);
|
|
2707
4398
|
}
|
|
2708
4399
|
|
|
2709
4400
|
// src/widgets/DateWidget.tsx
|
|
2710
4401
|
import { DateInput } from "@petrarca/sonnet-ui";
|
|
2711
|
-
import { jsx as
|
|
4402
|
+
import { jsx as jsx33 } from "react/jsx-runtime";
|
|
2712
4403
|
function resolvePrecision(value) {
|
|
2713
4404
|
return value === "year" || value === "month" ? value : "day";
|
|
2714
4405
|
}
|
|
@@ -2723,12 +4414,13 @@ function DateWidget({
|
|
|
2723
4414
|
label,
|
|
2724
4415
|
description,
|
|
2725
4416
|
placeholder,
|
|
2726
|
-
options
|
|
4417
|
+
options,
|
|
4418
|
+
propertySchema
|
|
2727
4419
|
}) {
|
|
2728
4420
|
const error = firstError(rawErrors);
|
|
2729
4421
|
const precision = resolvePrecision(options?.precision);
|
|
2730
4422
|
const locale = typeof options?.locale === "string" ? options.locale : void 0;
|
|
2731
|
-
return /* @__PURE__ */
|
|
4423
|
+
return /* @__PURE__ */ jsx33(
|
|
2732
4424
|
FormFieldWrapper,
|
|
2733
4425
|
{
|
|
2734
4426
|
inputId: id,
|
|
@@ -2736,7 +4428,7 @@ function DateWidget({
|
|
|
2736
4428
|
description,
|
|
2737
4429
|
required,
|
|
2738
4430
|
labelPlacement: options?.labelPlacement,
|
|
2739
|
-
children: /* @__PURE__ */
|
|
4431
|
+
children: /* @__PURE__ */ jsx33(
|
|
2740
4432
|
DateInput,
|
|
2741
4433
|
{
|
|
2742
4434
|
id,
|
|
@@ -2748,7 +4440,9 @@ function DateWidget({
|
|
|
2748
4440
|
placeholder,
|
|
2749
4441
|
required,
|
|
2750
4442
|
disabled,
|
|
2751
|
-
error
|
|
4443
|
+
error,
|
|
4444
|
+
min: propertySchema.formatMinimum,
|
|
4445
|
+
max: propertySchema.formatMaximum
|
|
2752
4446
|
}
|
|
2753
4447
|
)
|
|
2754
4448
|
}
|
|
@@ -2757,7 +4451,7 @@ function DateWidget({
|
|
|
2757
4451
|
|
|
2758
4452
|
// src/widgets/TimeWidget.tsx
|
|
2759
4453
|
import { TimeInput } from "@petrarca/sonnet-ui";
|
|
2760
|
-
import { jsx as
|
|
4454
|
+
import { jsx as jsx34 } from "react/jsx-runtime";
|
|
2761
4455
|
function resolveGranularity(value) {
|
|
2762
4456
|
return value === "second" ? "second" : "minute";
|
|
2763
4457
|
}
|
|
@@ -2776,7 +4470,7 @@ function TimeWidget({
|
|
|
2776
4470
|
}) {
|
|
2777
4471
|
const error = firstError(rawErrors);
|
|
2778
4472
|
const granularity = resolveGranularity(options?.granularity);
|
|
2779
|
-
return /* @__PURE__ */
|
|
4473
|
+
return /* @__PURE__ */ jsx34(
|
|
2780
4474
|
FormFieldWrapper,
|
|
2781
4475
|
{
|
|
2782
4476
|
inputId: id,
|
|
@@ -2784,7 +4478,7 @@ function TimeWidget({
|
|
|
2784
4478
|
description,
|
|
2785
4479
|
required,
|
|
2786
4480
|
labelPlacement: options?.labelPlacement,
|
|
2787
|
-
children: /* @__PURE__ */
|
|
4481
|
+
children: /* @__PURE__ */ jsx34(
|
|
2788
4482
|
TimeInput,
|
|
2789
4483
|
{
|
|
2790
4484
|
id,
|
|
@@ -2804,7 +4498,7 @@ function TimeWidget({
|
|
|
2804
4498
|
|
|
2805
4499
|
// src/widgets/DateTimeWidget.tsx
|
|
2806
4500
|
import { DateTimeInput } from "@petrarca/sonnet-ui";
|
|
2807
|
-
import { jsx as
|
|
4501
|
+
import { jsx as jsx35 } from "react/jsx-runtime";
|
|
2808
4502
|
function resolveGranularity2(value) {
|
|
2809
4503
|
return value === "second" ? "second" : "minute";
|
|
2810
4504
|
}
|
|
@@ -2818,13 +4512,14 @@ function DateTimeWidget({
|
|
|
2818
4512
|
rawErrors,
|
|
2819
4513
|
label,
|
|
2820
4514
|
description,
|
|
2821
|
-
options
|
|
4515
|
+
options,
|
|
4516
|
+
propertySchema
|
|
2822
4517
|
}) {
|
|
2823
4518
|
const error = firstError(rawErrors);
|
|
2824
4519
|
const granularity = resolveGranularity2(options?.granularity);
|
|
2825
4520
|
const timeZone = typeof options?.timeZone === "string" ? options.timeZone : void 0;
|
|
2826
4521
|
const locale = typeof options?.locale === "string" ? options.locale : void 0;
|
|
2827
|
-
return /* @__PURE__ */
|
|
4522
|
+
return /* @__PURE__ */ jsx35(
|
|
2828
4523
|
FormFieldWrapper,
|
|
2829
4524
|
{
|
|
2830
4525
|
inputId: id,
|
|
@@ -2832,7 +4527,7 @@ function DateTimeWidget({
|
|
|
2832
4527
|
description,
|
|
2833
4528
|
required,
|
|
2834
4529
|
labelPlacement: options?.labelPlacement,
|
|
2835
|
-
children: /* @__PURE__ */
|
|
4530
|
+
children: /* @__PURE__ */ jsx35(
|
|
2836
4531
|
DateTimeInput,
|
|
2837
4532
|
{
|
|
2838
4533
|
idPrefix: id,
|
|
@@ -2844,7 +4539,9 @@ function DateTimeWidget({
|
|
|
2844
4539
|
locale,
|
|
2845
4540
|
required,
|
|
2846
4541
|
disabled,
|
|
2847
|
-
error
|
|
4542
|
+
error,
|
|
4543
|
+
min: propertySchema.formatMinimum,
|
|
4544
|
+
max: propertySchema.formatMaximum
|
|
2848
4545
|
}
|
|
2849
4546
|
)
|
|
2850
4547
|
}
|
|
@@ -2852,20 +4549,20 @@ function DateTimeWidget({
|
|
|
2852
4549
|
}
|
|
2853
4550
|
|
|
2854
4551
|
// src/widgets/DisplayWidget.tsx
|
|
2855
|
-
import { jsx as
|
|
4552
|
+
import { jsx as jsx36, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2856
4553
|
function DisplayWidget({
|
|
2857
4554
|
label,
|
|
2858
4555
|
description
|
|
2859
4556
|
}) {
|
|
2860
4557
|
if (!label && !description) return null;
|
|
2861
|
-
return /* @__PURE__ */
|
|
2862
|
-
label && /* @__PURE__ */
|
|
2863
|
-
description && /* @__PURE__ */
|
|
4558
|
+
return /* @__PURE__ */ jsxs22("div", { className: "space-y-1", children: [
|
|
4559
|
+
label && /* @__PURE__ */ jsx36("p", { className: "text-sm text-foreground", children: label }),
|
|
4560
|
+
description && /* @__PURE__ */ jsx36("p", { className: "text-sm text-muted-foreground", children: description })
|
|
2864
4561
|
] });
|
|
2865
4562
|
}
|
|
2866
4563
|
|
|
2867
4564
|
// src/widgets/OneOfWidget.tsx
|
|
2868
|
-
import { useMemo as
|
|
4565
|
+
import { useMemo as useMemo8 } from "react";
|
|
2869
4566
|
import {
|
|
2870
4567
|
getDiscriminator,
|
|
2871
4568
|
getBranchValue,
|
|
@@ -2880,7 +4577,7 @@ import {
|
|
|
2880
4577
|
SelectContent,
|
|
2881
4578
|
SelectItem
|
|
2882
4579
|
} from "@petrarca/sonnet-ui";
|
|
2883
|
-
import { jsx as
|
|
4580
|
+
import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2884
4581
|
function buildChoices(branches, discriminator) {
|
|
2885
4582
|
return branches.map((branch, index) => {
|
|
2886
4583
|
const raw = discriminator ? getBranchValue(branch, discriminator) : void 0;
|
|
@@ -2916,15 +4613,20 @@ function BranchSelector({
|
|
|
2916
4613
|
disabled,
|
|
2917
4614
|
onSelect
|
|
2918
4615
|
}) {
|
|
2919
|
-
return /* @__PURE__ */
|
|
2920
|
-
label && /* @__PURE__ */
|
|
2921
|
-
/* @__PURE__ */
|
|
2922
|
-
/* @__PURE__ */
|
|
2923
|
-
/* @__PURE__ */
|
|
4616
|
+
return /* @__PURE__ */ jsxs23("div", { className: "space-y-1", children: [
|
|
4617
|
+
label && /* @__PURE__ */ jsx37(Label, { htmlFor: id, children: label }),
|
|
4618
|
+
/* @__PURE__ */ jsxs23(Select, { value: activeValue, onValueChange: onSelect, disabled, children: [
|
|
4619
|
+
/* @__PURE__ */ jsx37(SelectTrigger, { id, children: /* @__PURE__ */ jsx37(SelectValue, { placeholder: "Select an option" }) }),
|
|
4620
|
+
/* @__PURE__ */ jsx37(SelectContent, { children: choices.map((c) => /* @__PURE__ */ jsx37(SelectItem, { value: c.value, children: c.label }, c.value)) })
|
|
2924
4621
|
] }),
|
|
2925
|
-
description && /* @__PURE__ */
|
|
4622
|
+
description && /* @__PURE__ */ jsx37("p", { className: "text-sm text-muted-foreground", children: description })
|
|
2926
4623
|
] });
|
|
2927
4624
|
}
|
|
4625
|
+
function branchUiSchema(uiSchema, discriminator) {
|
|
4626
|
+
const fields = uiSchema?.["x-ui-fields"] ?? {};
|
|
4627
|
+
if (!discriminator) return fields;
|
|
4628
|
+
return { [discriminator]: { "x-ui-options": { hidden: true } }, ...fields };
|
|
4629
|
+
}
|
|
2928
4630
|
function resolveActive(propertySchema, branches, choices, discriminator, data) {
|
|
2929
4631
|
const index = findActiveBranch(propertySchema, data, discriminator);
|
|
2930
4632
|
if (index < 0) return { value: "", label: "", subSchema: null };
|
|
@@ -2949,12 +4651,12 @@ function OneOfWidget({
|
|
|
2949
4651
|
}) {
|
|
2950
4652
|
const { renderer: Renderer, templates } = useNestedFormContext();
|
|
2951
4653
|
const ContainerTemplate = templates.ObjectContainerTemplate ?? DefaultObjectContainerTemplate;
|
|
2952
|
-
const branches =
|
|
2953
|
-
const discriminator =
|
|
4654
|
+
const branches = useMemo8(() => propertySchema.oneOf ?? [], [propertySchema]);
|
|
4655
|
+
const discriminator = useMemo8(
|
|
2954
4656
|
() => getDiscriminator(propertySchema),
|
|
2955
4657
|
[propertySchema]
|
|
2956
4658
|
);
|
|
2957
|
-
const choices =
|
|
4659
|
+
const choices = useMemo8(
|
|
2958
4660
|
() => buildChoices(branches, discriminator),
|
|
2959
4661
|
[branches, discriminator]
|
|
2960
4662
|
);
|
|
@@ -2967,9 +4669,9 @@ function OneOfWidget({
|
|
|
2967
4669
|
data
|
|
2968
4670
|
);
|
|
2969
4671
|
if (branches.length === 0) return null;
|
|
2970
|
-
const subUiSchema = uiSchema
|
|
2971
|
-
return /* @__PURE__ */
|
|
2972
|
-
/* @__PURE__ */
|
|
4672
|
+
const subUiSchema = branchUiSchema(uiSchema, discriminator);
|
|
4673
|
+
return /* @__PURE__ */ jsxs23("div", { className: "space-y-4", children: [
|
|
4674
|
+
/* @__PURE__ */ jsx37(
|
|
2973
4675
|
BranchSelector,
|
|
2974
4676
|
{
|
|
2975
4677
|
id,
|
|
@@ -2981,14 +4683,14 @@ function OneOfWidget({
|
|
|
2981
4683
|
onSelect: (selected) => onChange(branchChangeData(choices, branches, discriminator, selected))
|
|
2982
4684
|
}
|
|
2983
4685
|
),
|
|
2984
|
-
active.subSchema && /* @__PURE__ */
|
|
4686
|
+
active.subSchema && /* @__PURE__ */ jsx37(
|
|
2985
4687
|
ContainerTemplate,
|
|
2986
4688
|
{
|
|
2987
4689
|
label: active.label,
|
|
2988
4690
|
description: void 0,
|
|
2989
4691
|
variant: options?.variant || "section",
|
|
2990
4692
|
defaultOpen: true,
|
|
2991
|
-
children: /* @__PURE__ */
|
|
4693
|
+
children: /* @__PURE__ */ jsx37(
|
|
2992
4694
|
Renderer,
|
|
2993
4695
|
{
|
|
2994
4696
|
schema: active.subSchema,
|
|
@@ -3006,8 +4708,304 @@ function OneOfWidget({
|
|
|
3006
4708
|
] });
|
|
3007
4709
|
}
|
|
3008
4710
|
|
|
4711
|
+
// src/widgets/RadioWidget.tsx
|
|
4712
|
+
import { RadioGroup, RadioGroupItem } from "@petrarca/sonnet-ui";
|
|
4713
|
+
import { cn as cn14 } from "@petrarca/sonnet-core";
|
|
4714
|
+
import { jsx as jsx38, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
4715
|
+
function RadioWidget({
|
|
4716
|
+
id,
|
|
4717
|
+
name,
|
|
4718
|
+
value,
|
|
4719
|
+
onChange,
|
|
4720
|
+
onBlur,
|
|
4721
|
+
propertySchema,
|
|
4722
|
+
required,
|
|
4723
|
+
disabled,
|
|
4724
|
+
readonly,
|
|
4725
|
+
rawErrors,
|
|
4726
|
+
label,
|
|
4727
|
+
description,
|
|
4728
|
+
options
|
|
4729
|
+
}) {
|
|
4730
|
+
const choices = buildSelectOptions(propertySchema, options?.enumNames ?? {});
|
|
4731
|
+
const horizontal = options?.orientation === "horizontal";
|
|
4732
|
+
return /* @__PURE__ */ jsx38(
|
|
4733
|
+
FormFieldWrapper,
|
|
4734
|
+
{
|
|
4735
|
+
inputId: id,
|
|
4736
|
+
label,
|
|
4737
|
+
description,
|
|
4738
|
+
error: firstError(rawErrors),
|
|
4739
|
+
required,
|
|
4740
|
+
compact: options?.compact,
|
|
4741
|
+
children: /* @__PURE__ */ jsx38(
|
|
4742
|
+
RadioGroup,
|
|
4743
|
+
{
|
|
4744
|
+
id,
|
|
4745
|
+
name,
|
|
4746
|
+
value: value === void 0 || value === null ? "" : String(value),
|
|
4747
|
+
onValueChange: (v) => onChange(v),
|
|
4748
|
+
onBlur: () => onBlur?.(id, value),
|
|
4749
|
+
disabled: disabled || readonly,
|
|
4750
|
+
required,
|
|
4751
|
+
className: cn14(horizontal && "flex flex-wrap gap-4"),
|
|
4752
|
+
children: choices.map((c) => /* @__PURE__ */ jsxs24("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
4753
|
+
/* @__PURE__ */ jsx38(RadioGroupItem, { value: c.value, id: `${id}-${c.value}` }),
|
|
4754
|
+
c.label
|
|
4755
|
+
] }, c.value))
|
|
4756
|
+
}
|
|
4757
|
+
)
|
|
4758
|
+
}
|
|
4759
|
+
);
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4762
|
+
// src/widgets/CheckboxGroupWidget.tsx
|
|
4763
|
+
import { Checkbox as Checkbox2 } from "@petrarca/sonnet-ui";
|
|
4764
|
+
import { cn as cn15 } from "@petrarca/sonnet-core";
|
|
4765
|
+
import { jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
4766
|
+
function CheckboxGroupWidget({
|
|
4767
|
+
id,
|
|
4768
|
+
value,
|
|
4769
|
+
onChange,
|
|
4770
|
+
onBlur,
|
|
4771
|
+
propertySchema,
|
|
4772
|
+
required,
|
|
4773
|
+
disabled,
|
|
4774
|
+
readonly,
|
|
4775
|
+
rawErrors,
|
|
4776
|
+
label,
|
|
4777
|
+
description,
|
|
4778
|
+
options
|
|
4779
|
+
}) {
|
|
4780
|
+
const choices = buildSelectOptions(propertySchema, options?.enumNames ?? {});
|
|
4781
|
+
const chosen = new Set(Array.isArray(value) ? value.map(String) : []);
|
|
4782
|
+
const toggle = (v, on) => {
|
|
4783
|
+
const next = choices.map((c) => c.value).filter((c) => c === v ? on : chosen.has(c));
|
|
4784
|
+
onChange(next);
|
|
4785
|
+
};
|
|
4786
|
+
return /* @__PURE__ */ jsx39(
|
|
4787
|
+
FormFieldWrapper,
|
|
4788
|
+
{
|
|
4789
|
+
inputId: id,
|
|
4790
|
+
label,
|
|
4791
|
+
description,
|
|
4792
|
+
error: firstError(rawErrors),
|
|
4793
|
+
required,
|
|
4794
|
+
compact: options?.compact,
|
|
4795
|
+
children: /* @__PURE__ */ jsx39(
|
|
4796
|
+
"div",
|
|
4797
|
+
{
|
|
4798
|
+
id,
|
|
4799
|
+
role: "group",
|
|
4800
|
+
className: cn15(
|
|
4801
|
+
"grid gap-2",
|
|
4802
|
+
options?.orientation === "horizontal" && "flex flex-wrap gap-4"
|
|
4803
|
+
),
|
|
4804
|
+
onBlur: () => onBlur?.(id, value),
|
|
4805
|
+
children: choices.map((c) => /* @__PURE__ */ jsxs25("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
4806
|
+
/* @__PURE__ */ jsx39(
|
|
4807
|
+
Checkbox2,
|
|
4808
|
+
{
|
|
4809
|
+
id: `${id}-${c.value}`,
|
|
4810
|
+
checked: chosen.has(c.value),
|
|
4811
|
+
onCheckedChange: (on) => toggle(c.value, on === true),
|
|
4812
|
+
disabled: disabled || readonly
|
|
4813
|
+
}
|
|
4814
|
+
),
|
|
4815
|
+
c.label
|
|
4816
|
+
] }, c.value))
|
|
4817
|
+
}
|
|
4818
|
+
)
|
|
4819
|
+
}
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4823
|
+
// src/widgets/ChoiceWithTextWidget.tsx
|
|
4824
|
+
import { jsx as jsx40 } from "react/jsx-runtime";
|
|
4825
|
+
var ROW_LAYOUT = {
|
|
4826
|
+
rows: [{ fields: ["choice", "text"], widths: ["md", "full"] }]
|
|
4827
|
+
};
|
|
4828
|
+
function textTitleOf(prop) {
|
|
4829
|
+
const text = prop.properties?.text ?? prop.allOf?.[0]?.then?.properties?.text;
|
|
4830
|
+
return text?.title;
|
|
4831
|
+
}
|
|
4832
|
+
function containerUiSchema(props) {
|
|
4833
|
+
const fields = props.uiSchema?.["x-ui-fields"] ?? {};
|
|
4834
|
+
const textTitle = textTitleOf(props.propertySchema);
|
|
4835
|
+
const layout = props.uiSchema?.["x-ui-layout"];
|
|
4836
|
+
return {
|
|
4837
|
+
...props.uiSchema,
|
|
4838
|
+
"x-ui-layout": layout ?? ROW_LAYOUT,
|
|
4839
|
+
"x-ui-fields": {
|
|
4840
|
+
...fields,
|
|
4841
|
+
choice: { "x-ui-title": false, ...fields.choice },
|
|
4842
|
+
text: {
|
|
4843
|
+
"x-ui-title": false,
|
|
4844
|
+
...textTitle ? { "x-ui-placeholder": textTitle } : {},
|
|
4845
|
+
...fields.text
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
};
|
|
4849
|
+
}
|
|
4850
|
+
function ChoiceWithTextWidget(props) {
|
|
4851
|
+
return /* @__PURE__ */ jsx40(
|
|
4852
|
+
FormFieldWrapper,
|
|
4853
|
+
{
|
|
4854
|
+
inputId: props.id,
|
|
4855
|
+
label: props.label,
|
|
4856
|
+
description: props.description,
|
|
4857
|
+
required: props.required,
|
|
4858
|
+
compact: props.options?.compact,
|
|
4859
|
+
children: /* @__PURE__ */ jsx40(
|
|
4860
|
+
ObjectWidget,
|
|
4861
|
+
{
|
|
4862
|
+
...props,
|
|
4863
|
+
uiSchema: containerUiSchema(props),
|
|
4864
|
+
options: { ...props.options, variant: "inline" }
|
|
4865
|
+
}
|
|
4866
|
+
)
|
|
4867
|
+
}
|
|
4868
|
+
);
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4871
|
+
// src/widgets/AbsentReasonsWidget.tsx
|
|
4872
|
+
import {
|
|
4873
|
+
Button as Button9,
|
|
4874
|
+
IconButton as IconButton2,
|
|
4875
|
+
Select as Select2,
|
|
4876
|
+
SelectContent as SelectContent2,
|
|
4877
|
+
SelectItem as SelectItem2,
|
|
4878
|
+
SelectTrigger as SelectTrigger2,
|
|
4879
|
+
SelectValue as SelectValue2
|
|
4880
|
+
} from "@petrarca/sonnet-ui";
|
|
4881
|
+
import { Plus as Plus5, X as X7 } from "lucide-react";
|
|
4882
|
+
import { jsx as jsx41, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
4883
|
+
function Pick({
|
|
4884
|
+
label,
|
|
4885
|
+
value,
|
|
4886
|
+
items,
|
|
4887
|
+
onChange,
|
|
4888
|
+
className
|
|
4889
|
+
}) {
|
|
4890
|
+
return /* @__PURE__ */ jsxs26(Select2, { value: value || void 0, onValueChange: onChange, children: [
|
|
4891
|
+
/* @__PURE__ */ jsx41(SelectTrigger2, { "aria-label": label, className, children: /* @__PURE__ */ jsx41(SelectValue2, { placeholder: label.toLowerCase() }) }),
|
|
4892
|
+
/* @__PURE__ */ jsx41(SelectContent2, { children: items.map((i) => /* @__PURE__ */ jsx41(SelectItem2, { value: i.value, children: i.label }, i.value)) })
|
|
4893
|
+
] });
|
|
4894
|
+
}
|
|
4895
|
+
function questionsOf(props) {
|
|
4896
|
+
return Object.entries(props.schema.properties ?? {}).filter(([key, p]) => key !== props.name && p.type !== "null").map(([key, p]) => ({ value: key, label: p.title ?? key }));
|
|
4897
|
+
}
|
|
4898
|
+
function ReasonRow({
|
|
4899
|
+
questionKey,
|
|
4900
|
+
reason,
|
|
4901
|
+
given,
|
|
4902
|
+
questions,
|
|
4903
|
+
reasons,
|
|
4904
|
+
locked,
|
|
4905
|
+
set
|
|
4906
|
+
}) {
|
|
4907
|
+
const { [questionKey]: _, ...rest } = given;
|
|
4908
|
+
return /* @__PURE__ */ jsxs26("div", { className: "group/row flex items-center gap-1.5 px-1.5 py-1", children: [
|
|
4909
|
+
/* @__PURE__ */ jsx41(
|
|
4910
|
+
Pick,
|
|
4911
|
+
{
|
|
4912
|
+
label: "Question",
|
|
4913
|
+
value: questionKey,
|
|
4914
|
+
items: questions.filter(
|
|
4915
|
+
(q) => q.value === questionKey || !(q.value in given)
|
|
4916
|
+
),
|
|
4917
|
+
onChange: (next) => set({ ...rest, [next]: reason }),
|
|
4918
|
+
className: "h-7 min-w-0 flex-1 text-xs"
|
|
4919
|
+
}
|
|
4920
|
+
),
|
|
4921
|
+
/* @__PURE__ */ jsx41(
|
|
4922
|
+
Pick,
|
|
4923
|
+
{
|
|
4924
|
+
label: "Reason",
|
|
4925
|
+
value: reason,
|
|
4926
|
+
items: reasons,
|
|
4927
|
+
onChange: (r) => set({ ...given, [questionKey]: r }),
|
|
4928
|
+
className: "h-7 w-40 shrink-0 text-xs"
|
|
4929
|
+
}
|
|
4930
|
+
),
|
|
4931
|
+
!locked && /* @__PURE__ */ jsx41(
|
|
4932
|
+
IconButton2,
|
|
4933
|
+
{
|
|
4934
|
+
label: "Answer it after all",
|
|
4935
|
+
revealOnHover: true,
|
|
4936
|
+
onClick: () => set(rest),
|
|
4937
|
+
children: /* @__PURE__ */ jsx41(X7, {})
|
|
4938
|
+
}
|
|
4939
|
+
)
|
|
4940
|
+
] });
|
|
4941
|
+
}
|
|
4942
|
+
function reasonsState(props) {
|
|
4943
|
+
const { value, readonly, disabled, options } = props;
|
|
4944
|
+
const additional = props.propertySchema.additionalProperties;
|
|
4945
|
+
const reasons = buildSelectOptions(
|
|
4946
|
+
additional ?? {},
|
|
4947
|
+
options?.enumNames ?? {}
|
|
4948
|
+
);
|
|
4949
|
+
const given = value && typeof value === "object" ? value : {};
|
|
4950
|
+
const questions = questionsOf(props);
|
|
4951
|
+
const free = questions.filter((q) => !(q.value in given));
|
|
4952
|
+
const locked = readonly || disabled;
|
|
4953
|
+
return {
|
|
4954
|
+
reasons,
|
|
4955
|
+
given,
|
|
4956
|
+
questions,
|
|
4957
|
+
free,
|
|
4958
|
+
locked,
|
|
4959
|
+
canAdd: !locked && free.length > 0 && reasons.length > 0
|
|
4960
|
+
};
|
|
4961
|
+
}
|
|
4962
|
+
function AbsentReasonsWidget(props) {
|
|
4963
|
+
const { id, onChange, label, rawErrors, options } = props;
|
|
4964
|
+
const { reasons, given, questions, free, locked, canAdd } = reasonsState(props);
|
|
4965
|
+
const set = (next) => onChange(Object.keys(next).length ? next : void 0);
|
|
4966
|
+
return /* @__PURE__ */ jsx41(
|
|
4967
|
+
FormFieldWrapper,
|
|
4968
|
+
{
|
|
4969
|
+
inputId: id,
|
|
4970
|
+
label,
|
|
4971
|
+
error: firstError(rawErrors),
|
|
4972
|
+
compact: options?.compact,
|
|
4973
|
+
children: /* @__PURE__ */ jsxs26("div", { className: "space-y-1.5", children: [
|
|
4974
|
+
Object.keys(given).length > 0 && /* @__PURE__ */ jsx41("div", { className: "rounded-md border divide-y", children: Object.entries(given).map(([key, reason]) => /* @__PURE__ */ jsx41(
|
|
4975
|
+
ReasonRow,
|
|
4976
|
+
{
|
|
4977
|
+
questionKey: key,
|
|
4978
|
+
reason,
|
|
4979
|
+
given,
|
|
4980
|
+
questions,
|
|
4981
|
+
reasons,
|
|
4982
|
+
locked,
|
|
4983
|
+
set
|
|
4984
|
+
},
|
|
4985
|
+
key
|
|
4986
|
+
)) }),
|
|
4987
|
+
canAdd && /* @__PURE__ */ jsxs26(
|
|
4988
|
+
Button9,
|
|
4989
|
+
{
|
|
4990
|
+
type: "button",
|
|
4991
|
+
variant: "ghost",
|
|
4992
|
+
size: "sm",
|
|
4993
|
+
className: "h-7 gap-1 px-2 text-xs",
|
|
4994
|
+
onClick: () => set({ ...given, [free[0].value]: reasons[0].value }),
|
|
4995
|
+
children: [
|
|
4996
|
+
/* @__PURE__ */ jsx41(Plus5, { className: "h-3.5 w-3.5" }),
|
|
4997
|
+
"Leave a question unanswered"
|
|
4998
|
+
]
|
|
4999
|
+
}
|
|
5000
|
+
)
|
|
5001
|
+
] })
|
|
5002
|
+
}
|
|
5003
|
+
);
|
|
5004
|
+
}
|
|
5005
|
+
|
|
3009
5006
|
// src/widgets/widgetResolver.ts
|
|
3010
5007
|
import {
|
|
5008
|
+
bindingOf as bindingOf3,
|
|
3011
5009
|
isNullableBoolean as isNullableBoolean2,
|
|
3012
5010
|
isOneOf,
|
|
3013
5011
|
extractEnumValues as extractEnumValues2,
|
|
@@ -3084,20 +5082,45 @@ function resolveFromUiWidget(uiWidget, propertySchema, registry) {
|
|
|
3084
5082
|
function isObjectArraySchema(schema) {
|
|
3085
5083
|
return schema.type === "array" && schema.items?.type === "object" && !!schema.items?.properties;
|
|
3086
5084
|
}
|
|
5085
|
+
function isStringArraySchema(schema) {
|
|
5086
|
+
return schema.type === "array" && schema.items?.type === "string" && !Array.isArray(schema.items?.enum);
|
|
5087
|
+
}
|
|
5088
|
+
function isStringMapSchema(schema) {
|
|
5089
|
+
const extra = schema.additionalProperties;
|
|
5090
|
+
return schema.type === "object" && !schema.properties && typeof extra === "object" && extra !== null && extra.type === "string";
|
|
5091
|
+
}
|
|
5092
|
+
function isEnumArraySchema(schema) {
|
|
5093
|
+
return schema.type === "array" && Array.isArray(schema.items?.enum);
|
|
5094
|
+
}
|
|
5095
|
+
function isCodedShape(schema) {
|
|
5096
|
+
return schema.type === "string" || schema.type === "array" || !!schema.anyOf;
|
|
5097
|
+
}
|
|
3087
5098
|
function registryLookup(registry, widgetName) {
|
|
3088
5099
|
return registry[widgetName] || registry["TextInputWidget"];
|
|
3089
5100
|
}
|
|
5101
|
+
function objectWidgetName(schema) {
|
|
5102
|
+
if (schema.properties) return "ObjectWidget";
|
|
5103
|
+
if (isStringMapSchema(schema)) return "KeyValueWidget";
|
|
5104
|
+
return "JsonEditorWidget";
|
|
5105
|
+
}
|
|
5106
|
+
function arrayWidgetName(schema) {
|
|
5107
|
+
if (isObjectArraySchema(schema)) return "ArrayWidget";
|
|
5108
|
+
if (isEnumArraySchema(schema)) return "SelectWidget";
|
|
5109
|
+
if (isStringArraySchema(schema)) return "TagsInputWidget";
|
|
5110
|
+
return null;
|
|
5111
|
+
}
|
|
3090
5112
|
function resolveFromSchemaStructure(schema, registry) {
|
|
3091
5113
|
if (isOneOf(schema)) return registryLookup(registry, "OneOfWidget");
|
|
3092
5114
|
if (isNullableBoolean2(schema))
|
|
3093
5115
|
return registryLookup(registry, "CheckboxWidget");
|
|
3094
5116
|
if (extractEnumValues2(schema).length > 0)
|
|
3095
5117
|
return registryLookup(registry, "SelectWidget");
|
|
3096
|
-
if (schema
|
|
3097
|
-
return registryLookup(registry, "
|
|
3098
|
-
if (
|
|
3099
|
-
return registryLookup(registry,
|
|
3100
|
-
|
|
5118
|
+
if (bindingOf3(schema) && isCodedShape(schema))
|
|
5119
|
+
return registryLookup(registry, "CodedValueWidget");
|
|
5120
|
+
if (schema.type === "object")
|
|
5121
|
+
return registryLookup(registry, objectWidgetName(schema));
|
|
5122
|
+
const arrayWidget = arrayWidgetName(schema);
|
|
5123
|
+
return arrayWidget ? registryLookup(registry, arrayWidget) : null;
|
|
3101
5124
|
}
|
|
3102
5125
|
function resolveFromTypeFormat(schema, registry) {
|
|
3103
5126
|
const type = getFieldType(schema);
|
|
@@ -3128,15 +5151,22 @@ var DEFAULT_WIDGETS = {
|
|
|
3128
5151
|
SelectWidget,
|
|
3129
5152
|
TagsInputWidget,
|
|
3130
5153
|
JsonEditorWidget,
|
|
5154
|
+
KeyValueWidget,
|
|
3131
5155
|
EntitySelectWidget,
|
|
3132
5156
|
ObjectWidget,
|
|
3133
5157
|
ArrayWidget,
|
|
3134
5158
|
QuantityWidget,
|
|
5159
|
+
CodedValueWidget,
|
|
5160
|
+
CodeableConceptWidget,
|
|
3135
5161
|
DateWidget,
|
|
3136
5162
|
TimeWidget,
|
|
3137
5163
|
DateTimeWidget,
|
|
3138
5164
|
DisplayWidget,
|
|
3139
5165
|
OneOfWidget,
|
|
5166
|
+
RadioWidget,
|
|
5167
|
+
CheckboxGroupWidget,
|
|
5168
|
+
ChoiceWithTextWidget,
|
|
5169
|
+
AbsentReasonsWidget,
|
|
3140
5170
|
// Lowercase aliases for x-ui-widget convenience
|
|
3141
5171
|
text: TextInputWidget,
|
|
3142
5172
|
textarea: TextareaWidget,
|
|
@@ -3145,21 +5175,28 @@ var DEFAULT_WIDGETS = {
|
|
|
3145
5175
|
select: SelectWidget,
|
|
3146
5176
|
tags: TagsInputWidget,
|
|
3147
5177
|
"json-editor": JsonEditorWidget,
|
|
5178
|
+
"key-value": KeyValueWidget,
|
|
3148
5179
|
"entity-select": EntitySelectWidget,
|
|
3149
5180
|
object: ObjectWidget,
|
|
3150
5181
|
array: ArrayWidget,
|
|
3151
5182
|
quantity: QuantityWidget,
|
|
5183
|
+
"coded-value": CodedValueWidget,
|
|
5184
|
+
"codeable-concept": CodeableConceptWidget,
|
|
3152
5185
|
date: DateWidget,
|
|
3153
5186
|
time: TimeWidget,
|
|
3154
5187
|
"date-time": DateTimeWidget,
|
|
3155
5188
|
display: DisplayWidget,
|
|
3156
|
-
"one-of": OneOfWidget
|
|
5189
|
+
"one-of": OneOfWidget,
|
|
5190
|
+
radio: RadioWidget,
|
|
5191
|
+
"checkbox-group": CheckboxGroupWidget,
|
|
5192
|
+
"choice-with-text": ChoiceWithTextWidget,
|
|
5193
|
+
"absent-reasons": AbsentReasonsWidget
|
|
3157
5194
|
};
|
|
3158
5195
|
|
|
3159
5196
|
// src/templates/HorizontalArrayItemTemplate.tsx
|
|
3160
5197
|
import { Trash2 as Trash22, ChevronUp as ChevronUp2, ChevronDown as ChevronDown4 } from "lucide-react";
|
|
3161
|
-
import { Button as
|
|
3162
|
-
import { jsx as
|
|
5198
|
+
import { Button as Button10 } from "@petrarca/sonnet-ui";
|
|
5199
|
+
import { jsx as jsx42, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
3163
5200
|
function HorizontalArrayItemTemplate({
|
|
3164
5201
|
children,
|
|
3165
5202
|
index,
|
|
@@ -3183,7 +5220,7 @@ function HorizontalArrayItemTemplate({
|
|
|
3183
5220
|
);
|
|
3184
5221
|
const gap = resolvedLayout.gap ?? 8;
|
|
3185
5222
|
const alignItems = resolvedLayout.compact ? "center" : "start";
|
|
3186
|
-
return /* @__PURE__ */
|
|
5223
|
+
return /* @__PURE__ */ jsxs27(
|
|
3187
5224
|
"div",
|
|
3188
5225
|
{
|
|
3189
5226
|
"data-fid": fid,
|
|
@@ -3195,9 +5232,9 @@ function HorizontalArrayItemTemplate({
|
|
|
3195
5232
|
},
|
|
3196
5233
|
children: [
|
|
3197
5234
|
children,
|
|
3198
|
-
orderable && /* @__PURE__ */
|
|
3199
|
-
/* @__PURE__ */
|
|
3200
|
-
|
|
5235
|
+
orderable && /* @__PURE__ */ jsxs27("div", { className: "flex flex-col items-center justify-center", children: [
|
|
5236
|
+
/* @__PURE__ */ jsx42(
|
|
5237
|
+
Button10,
|
|
3201
5238
|
{
|
|
3202
5239
|
type: "button",
|
|
3203
5240
|
variant: "ghost",
|
|
@@ -3206,11 +5243,11 @@ function HorizontalArrayItemTemplate({
|
|
|
3206
5243
|
onClick: onMoveUp,
|
|
3207
5244
|
title: "Move up",
|
|
3208
5245
|
className: "h-4 w-8 p-0",
|
|
3209
|
-
children: /* @__PURE__ */
|
|
5246
|
+
children: /* @__PURE__ */ jsx42(ChevronUp2, { className: "h-3 w-3" })
|
|
3210
5247
|
}
|
|
3211
5248
|
),
|
|
3212
|
-
/* @__PURE__ */
|
|
3213
|
-
|
|
5249
|
+
/* @__PURE__ */ jsx42(
|
|
5250
|
+
Button10,
|
|
3214
5251
|
{
|
|
3215
5252
|
type: "button",
|
|
3216
5253
|
variant: "ghost",
|
|
@@ -3219,12 +5256,12 @@ function HorizontalArrayItemTemplate({
|
|
|
3219
5256
|
onClick: onMoveDown,
|
|
3220
5257
|
title: "Move down",
|
|
3221
5258
|
className: "h-4 w-8 p-0",
|
|
3222
|
-
children: /* @__PURE__ */
|
|
5259
|
+
children: /* @__PURE__ */ jsx42(ChevronDown4, { className: "h-3 w-3" })
|
|
3223
5260
|
}
|
|
3224
5261
|
)
|
|
3225
5262
|
] }),
|
|
3226
|
-
canRemove && /* @__PURE__ */
|
|
3227
|
-
|
|
5263
|
+
canRemove && /* @__PURE__ */ jsx42(
|
|
5264
|
+
Button10,
|
|
3228
5265
|
{
|
|
3229
5266
|
type: "button",
|
|
3230
5267
|
variant: "ghost",
|
|
@@ -3233,7 +5270,7 @@ function HorizontalArrayItemTemplate({
|
|
|
3233
5270
|
onClick: onRemove,
|
|
3234
5271
|
title: "Remove item",
|
|
3235
5272
|
className: "text-muted-foreground hover:text-destructive h-8 w-8 p-0",
|
|
3236
|
-
children: /* @__PURE__ */
|
|
5273
|
+
children: /* @__PURE__ */ jsx42(Trash22, { className: "h-3.5 w-3.5" })
|
|
3237
5274
|
}
|
|
3238
5275
|
)
|
|
3239
5276
|
]
|
|
@@ -3242,49 +5279,49 @@ function HorizontalArrayItemTemplate({
|
|
|
3242
5279
|
}
|
|
3243
5280
|
|
|
3244
5281
|
// src/templates/TabsGroupTemplate.tsx
|
|
3245
|
-
import { useState as
|
|
5282
|
+
import { useState as useState14 } from "react";
|
|
3246
5283
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@petrarca/sonnet-ui";
|
|
3247
5284
|
|
|
3248
5285
|
// src/templates/GroupErrorBadge.tsx
|
|
3249
5286
|
import { AlertCircle } from "lucide-react";
|
|
3250
|
-
import { jsx as
|
|
5287
|
+
import { jsx as jsx43 } from "react/jsx-runtime";
|
|
3251
5288
|
function GroupErrorBadge({
|
|
3252
5289
|
count
|
|
3253
5290
|
}) {
|
|
3254
5291
|
if (count === 0) return null;
|
|
3255
5292
|
const label = count === 1 ? "1 field needs attention" : `${count} fields need attention`;
|
|
3256
|
-
return /* @__PURE__ */
|
|
5293
|
+
return /* @__PURE__ */ jsx43(
|
|
3257
5294
|
"span",
|
|
3258
5295
|
{
|
|
3259
5296
|
role: "img",
|
|
3260
5297
|
"aria-label": label,
|
|
3261
5298
|
title: label,
|
|
3262
5299
|
className: "inline-flex shrink-0 text-destructive",
|
|
3263
|
-
children: /* @__PURE__ */
|
|
5300
|
+
children: /* @__PURE__ */ jsx43(AlertCircle, { className: "h-3.5 w-3.5" })
|
|
3264
5301
|
}
|
|
3265
5302
|
);
|
|
3266
5303
|
}
|
|
3267
5304
|
|
|
3268
5305
|
// src/templates/TabsGroupTemplate.tsx
|
|
3269
|
-
import { jsx as
|
|
5306
|
+
import { jsx as jsx44, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
3270
5307
|
function TabsGroupTemplate({
|
|
3271
5308
|
groups
|
|
3272
5309
|
}) {
|
|
3273
|
-
const [active, setActive] =
|
|
5310
|
+
const [active, setActive] = useState14(groups[0].key);
|
|
3274
5311
|
const value = groups.some((g) => g.key === active) ? active : groups[0].key;
|
|
3275
|
-
return /* @__PURE__ */
|
|
3276
|
-
/* @__PURE__ */
|
|
5312
|
+
return /* @__PURE__ */ jsxs28(Tabs, { value, onValueChange: setActive, children: [
|
|
5313
|
+
/* @__PURE__ */ jsx44(TabsList, { className: "mb-4", children: groups.map((g) => /* @__PURE__ */ jsxs28(TabsTrigger, { value: g.key, className: "gap-1.5", children: [
|
|
3277
5314
|
g.label,
|
|
3278
|
-
/* @__PURE__ */
|
|
5315
|
+
/* @__PURE__ */ jsx44(GroupErrorBadge, { count: g.errorCount })
|
|
3279
5316
|
] }, g.key)) }),
|
|
3280
|
-
groups.map((g) => /* @__PURE__ */
|
|
5317
|
+
groups.map((g) => /* @__PURE__ */ jsx44(TabsContent, { value: g.key, className: "space-y-4", children: g.content }, g.key))
|
|
3281
5318
|
] });
|
|
3282
5319
|
}
|
|
3283
5320
|
|
|
3284
5321
|
// src/templates/StepperGroupTemplate.tsx
|
|
3285
|
-
import { useState as
|
|
5322
|
+
import { useState as useState15 } from "react";
|
|
3286
5323
|
import {
|
|
3287
|
-
Button as
|
|
5324
|
+
Button as Button11,
|
|
3288
5325
|
Stepper,
|
|
3289
5326
|
StepperList,
|
|
3290
5327
|
StepperItem,
|
|
@@ -3296,47 +5333,47 @@ import {
|
|
|
3296
5333
|
StepperPrev,
|
|
3297
5334
|
StepperNext
|
|
3298
5335
|
} from "@petrarca/sonnet-ui";
|
|
3299
|
-
import { jsx as
|
|
5336
|
+
import { jsx as jsx45, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
3300
5337
|
function StepperGroupTemplate({
|
|
3301
5338
|
groups
|
|
3302
5339
|
}) {
|
|
3303
|
-
const [active, setActive] =
|
|
5340
|
+
const [active, setActive] = useState15(groups[0].key);
|
|
3304
5341
|
const value = groups.some((g) => g.key === active) ? active : groups[0].key;
|
|
3305
5342
|
const isLast = value === groups[groups.length - 1].key;
|
|
3306
|
-
return /* @__PURE__ */
|
|
3307
|
-
/* @__PURE__ */
|
|
3308
|
-
/* @__PURE__ */
|
|
3309
|
-
/* @__PURE__ */
|
|
3310
|
-
/* @__PURE__ */
|
|
5343
|
+
return /* @__PURE__ */ jsxs29(Stepper, { value, onValueChange: setActive, children: [
|
|
5344
|
+
/* @__PURE__ */ jsx45(StepperList, { className: "mb-4 gap-2", children: groups.map((g) => /* @__PURE__ */ jsxs29(StepperItem, { value: g.key, children: [
|
|
5345
|
+
/* @__PURE__ */ jsxs29(StepperTrigger, { children: [
|
|
5346
|
+
/* @__PURE__ */ jsx45(StepperIndicator, {}),
|
|
5347
|
+
/* @__PURE__ */ jsxs29(StepperTitle, { className: "inline-flex items-center gap-1.5", children: [
|
|
3311
5348
|
g.label,
|
|
3312
|
-
/* @__PURE__ */
|
|
5349
|
+
/* @__PURE__ */ jsx45(GroupErrorBadge, { count: g.errorCount })
|
|
3313
5350
|
] })
|
|
3314
5351
|
] }),
|
|
3315
|
-
/* @__PURE__ */
|
|
5352
|
+
/* @__PURE__ */ jsx45(StepperSeparator, {})
|
|
3316
5353
|
] }, g.key)) }),
|
|
3317
|
-
groups.map((g) => /* @__PURE__ */
|
|
3318
|
-
/* @__PURE__ */
|
|
3319
|
-
/* @__PURE__ */
|
|
3320
|
-
!isLast && /* @__PURE__ */
|
|
5354
|
+
groups.map((g) => /* @__PURE__ */ jsx45(StepperContent, { value: g.key, className: "space-y-4", children: g.content }, g.key)),
|
|
5355
|
+
/* @__PURE__ */ jsxs29("div", { className: "mt-4 flex justify-between", children: [
|
|
5356
|
+
/* @__PURE__ */ jsx45(StepperPrev, { asChild: true, children: /* @__PURE__ */ jsx45(Button11, { variant: "outline", size: "sm", children: "Back" }) }),
|
|
5357
|
+
!isLast && /* @__PURE__ */ jsx45(StepperNext, { asChild: true, children: /* @__PURE__ */ jsx45(Button11, { size: "sm", children: "Next" }) })
|
|
3321
5358
|
] })
|
|
3322
5359
|
] });
|
|
3323
5360
|
}
|
|
3324
5361
|
|
|
3325
5362
|
// src/templates/SectionsGroupTemplate.tsx
|
|
3326
|
-
import { jsx as
|
|
5363
|
+
import { jsx as jsx46, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
3327
5364
|
function SectionsGroupTemplate({
|
|
3328
5365
|
groups
|
|
3329
5366
|
}) {
|
|
3330
|
-
return /* @__PURE__ */
|
|
5367
|
+
return /* @__PURE__ */ jsx46("div", { className: "space-y-6", children: groups.map((g) => /* @__PURE__ */ jsxs30(
|
|
3331
5368
|
"fieldset",
|
|
3332
5369
|
{
|
|
3333
5370
|
className: "rounded-md border border-border/60 px-3 pb-3 pt-1",
|
|
3334
5371
|
children: [
|
|
3335
|
-
/* @__PURE__ */
|
|
5372
|
+
/* @__PURE__ */ jsxs30("legend", { className: "inline-flex items-center gap-1.5 px-1 text-sm font-medium text-muted-foreground", children: [
|
|
3336
5373
|
g.label,
|
|
3337
|
-
/* @__PURE__ */
|
|
5374
|
+
/* @__PURE__ */ jsx46(GroupErrorBadge, { count: g.errorCount })
|
|
3338
5375
|
] }),
|
|
3339
|
-
/* @__PURE__ */
|
|
5376
|
+
/* @__PURE__ */ jsx46("div", { className: "space-y-4", children: g.content })
|
|
3340
5377
|
]
|
|
3341
5378
|
},
|
|
3342
5379
|
g.key
|
|
@@ -3360,13 +5397,15 @@ function groupTemplateKey(variant) {
|
|
|
3360
5397
|
}
|
|
3361
5398
|
|
|
3362
5399
|
// src/FormFieldRenderer.tsx
|
|
5400
|
+
import { useMemo as useMemo9 } from "react";
|
|
3363
5401
|
import { warnLog } from "@petrarca/sonnet-core";
|
|
3364
5402
|
import {
|
|
3365
5403
|
getFieldTitle,
|
|
3366
5404
|
getFieldHelpText,
|
|
3367
|
-
getFieldPlaceholder
|
|
5405
|
+
getFieldPlaceholder,
|
|
5406
|
+
liftNullableMember
|
|
3368
5407
|
} from "@petrarca/sonnet-core/schema";
|
|
3369
|
-
import { jsx as
|
|
5408
|
+
import { jsx as jsx47 } from "react/jsx-runtime";
|
|
3370
5409
|
function mergeFieldOptions(globalOptions, property, uiSchema) {
|
|
3371
5410
|
const xWidgetOptions = property["x-ui-options"] || {};
|
|
3372
5411
|
const fieldOptions = uiSchema?.["x-ui-options"] || {};
|
|
@@ -3393,7 +5432,7 @@ function applyFieldWidth(widget, uiSchema, mergedOptions) {
|
|
|
3393
5432
|
}
|
|
3394
5433
|
const width = candidate && candidate in FIELD_WIDTH_CLASS ? candidate : "full";
|
|
3395
5434
|
if (width === "full") return widget;
|
|
3396
|
-
return /* @__PURE__ */
|
|
5435
|
+
return /* @__PURE__ */ jsx47("div", { className: FIELD_WIDTH_CLASS[width], children: widget });
|
|
3397
5436
|
}
|
|
3398
5437
|
function resolvePresentationText(property, uiSchema) {
|
|
3399
5438
|
return {
|
|
@@ -3409,7 +5448,7 @@ function resolveFieldFlags(fieldOptions, globalOptions, disabled, readOnly) {
|
|
|
3409
5448
|
}
|
|
3410
5449
|
function FormFieldRenderer({
|
|
3411
5450
|
fieldName,
|
|
3412
|
-
property,
|
|
5451
|
+
property: declared,
|
|
3413
5452
|
value,
|
|
3414
5453
|
onChange,
|
|
3415
5454
|
onBlur,
|
|
@@ -3424,6 +5463,7 @@ function FormFieldRenderer({
|
|
|
3424
5463
|
idPrefix = "field",
|
|
3425
5464
|
rawErrors
|
|
3426
5465
|
}) {
|
|
5466
|
+
const property = useMemo9(() => liftNullableMember(declared), [declared]);
|
|
3427
5467
|
const label = getFieldTitle(fieldName, property);
|
|
3428
5468
|
const { description, placeholder } = resolvePresentationText(
|
|
3429
5469
|
property,
|
|
@@ -3444,7 +5484,7 @@ function FormFieldRenderer({
|
|
|
3444
5484
|
uiSchema?.["x-ui-title"],
|
|
3445
5485
|
globalOptions.label
|
|
3446
5486
|
);
|
|
3447
|
-
const widget = /* @__PURE__ */
|
|
5487
|
+
const widget = /* @__PURE__ */ jsx47(
|
|
3448
5488
|
Widget,
|
|
3449
5489
|
{
|
|
3450
5490
|
id: `${idPrefix}-${fieldName}`,
|
|
@@ -3471,19 +5511,213 @@ function FormFieldRenderer({
|
|
|
3471
5511
|
}
|
|
3472
5512
|
|
|
3473
5513
|
// src/hooks/useFormEngine.ts
|
|
3474
|
-
import { useState as
|
|
5514
|
+
import { useState as useState17, useEffect as useEffect8, useMemo as useMemo11, useCallback as useCallback8, useRef as useRef8 } from "react";
|
|
3475
5515
|
import { isEqual } from "@petrarca/sonnet-core";
|
|
3476
5516
|
import {
|
|
3477
|
-
|
|
5517
|
+
isNullableBoolean as isNullableBoolean3,
|
|
5518
|
+
getSchemaProperties as getSchemaProperties4,
|
|
3478
5519
|
getFieldDefault,
|
|
3479
5520
|
applySchemaDefaults as applySchemaDefaults2,
|
|
3480
5521
|
resolveSchema,
|
|
3481
5522
|
resolveRefs,
|
|
3482
5523
|
validateFormData,
|
|
5524
|
+
stripFormItemIds,
|
|
5525
|
+
mergeFieldErrors,
|
|
3483
5526
|
diffFormData,
|
|
3484
5527
|
ensureFormItemIds
|
|
3485
5528
|
} from "@petrarca/sonnet-core/schema";
|
|
3486
5529
|
|
|
5530
|
+
// src/validators/runValidators.ts
|
|
5531
|
+
import { getSchemaProperties as getSchemaProperties2 } from "@petrarca/sonnet-core/schema";
|
|
5532
|
+
|
|
5533
|
+
// src/validators/shared.ts
|
|
5534
|
+
import { getSchemaProperties } from "@petrarca/sonnet-core/schema";
|
|
5535
|
+
function toRefs(v) {
|
|
5536
|
+
if (v === void 0) return [];
|
|
5537
|
+
const arr = Array.isArray(v) ? v : [v];
|
|
5538
|
+
return arr.map((item) => typeof item === "string" ? { name: item } : item);
|
|
5539
|
+
}
|
|
5540
|
+
function escapeToken(token) {
|
|
5541
|
+
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
5542
|
+
}
|
|
5543
|
+
function fieldPointer(fieldName) {
|
|
5544
|
+
return `/${escapeToken(fieldName)}`;
|
|
5545
|
+
}
|
|
5546
|
+
function resolveFieldRefs(fieldName, schema, uiSchema) {
|
|
5547
|
+
const property = getSchemaProperties(schema)[fieldName] ?? {};
|
|
5548
|
+
return toRefs(
|
|
5549
|
+
uiSchema[fieldName]?.["x-ui-validate"] ?? property["x-ui-validate"]
|
|
5550
|
+
);
|
|
5551
|
+
}
|
|
5552
|
+
function toFieldErrors(result, pointer) {
|
|
5553
|
+
if (result == null || result === "") return [];
|
|
5554
|
+
if (typeof result === "string") return [{ pointer, message: result }];
|
|
5555
|
+
if (Array.isArray(result)) return result;
|
|
5556
|
+
return [];
|
|
5557
|
+
}
|
|
5558
|
+
|
|
5559
|
+
// src/validators/runValidators.ts
|
|
5560
|
+
function isKnownName(registry, name) {
|
|
5561
|
+
return !!(registry.field?.[name] || registry.asyncField?.[name] || registry.form?.[name] || registry.asyncForm?.[name]);
|
|
5562
|
+
}
|
|
5563
|
+
function unknownValidator(kind, name, registry) {
|
|
5564
|
+
const registered = [
|
|
5565
|
+
...Object.keys(registry.field ?? {}),
|
|
5566
|
+
...Object.keys(registry.asyncField ?? {}),
|
|
5567
|
+
...Object.keys(registry.form ?? {}),
|
|
5568
|
+
...Object.keys(registry.asyncForm ?? {})
|
|
5569
|
+
];
|
|
5570
|
+
throw new Error(
|
|
5571
|
+
`unknown ${kind} validator '${name}' (registered: [${registered.join(", ")}])`
|
|
5572
|
+
);
|
|
5573
|
+
}
|
|
5574
|
+
function runFieldRefs(fieldName, data, registry, uiSchema, schema, out) {
|
|
5575
|
+
const refs = resolveFieldRefs(fieldName, schema, uiSchema);
|
|
5576
|
+
if (refs.length === 0) return;
|
|
5577
|
+
const pointer = fieldPointer(fieldName);
|
|
5578
|
+
const value = data[fieldName];
|
|
5579
|
+
for (const ref of refs) {
|
|
5580
|
+
if (!isKnownName(registry, ref.name)) {
|
|
5581
|
+
unknownValidator("field", ref.name, registry);
|
|
5582
|
+
}
|
|
5583
|
+
const fn = registry.field?.[ref.name];
|
|
5584
|
+
if (!fn) continue;
|
|
5585
|
+
const ctx = {
|
|
5586
|
+
formData: data,
|
|
5587
|
+
path: pointer,
|
|
5588
|
+
args: ref.args
|
|
5589
|
+
};
|
|
5590
|
+
out.push(...toFieldErrors(fn(value, ctx), pointer));
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
function runFormRefs(data, registry, schema, out) {
|
|
5594
|
+
for (const ref of toRefs(schema["x-ui-validate"])) {
|
|
5595
|
+
if (!isKnownName(registry, ref.name)) {
|
|
5596
|
+
unknownValidator("form", ref.name, registry);
|
|
5597
|
+
}
|
|
5598
|
+
const fn = registry.form?.[ref.name];
|
|
5599
|
+
if (!fn) continue;
|
|
5600
|
+
const ctx = { formData: data, path: "", args: ref.args };
|
|
5601
|
+
out.push(...fn(data, ctx));
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
function runValidators(schema, data, registry, uiSchema, includeFormValidators) {
|
|
5605
|
+
const out = [];
|
|
5606
|
+
if (!registry) return out;
|
|
5607
|
+
for (const fieldName of Object.keys(getSchemaProperties2(schema))) {
|
|
5608
|
+
runFieldRefs(fieldName, data, registry, uiSchema, schema, out);
|
|
5609
|
+
}
|
|
5610
|
+
if (includeFormValidators) {
|
|
5611
|
+
runFormRefs(data, registry, schema, out);
|
|
5612
|
+
}
|
|
5613
|
+
return out;
|
|
5614
|
+
}
|
|
5615
|
+
|
|
5616
|
+
// src/validators/useAsyncValidators.ts
|
|
5617
|
+
import { useEffect as useEffect7, useMemo as useMemo10, useRef as useRef7, useState as useState16 } from "react";
|
|
5618
|
+
import { getSchemaProperties as getSchemaProperties3 } from "@petrarca/sonnet-core/schema";
|
|
5619
|
+
function keyOf(value) {
|
|
5620
|
+
try {
|
|
5621
|
+
return JSON.stringify(value) ?? "undefined";
|
|
5622
|
+
} catch {
|
|
5623
|
+
return String(value);
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
function collectFieldCandidates(schema, data, registry, uiSchema, out) {
|
|
5627
|
+
for (const fieldName of Object.keys(getSchemaProperties3(schema))) {
|
|
5628
|
+
const refs = resolveFieldRefs(fieldName, schema, uiSchema);
|
|
5629
|
+
const pointer = fieldPointer(fieldName);
|
|
5630
|
+
const value = data[fieldName];
|
|
5631
|
+
for (const ref of refs) {
|
|
5632
|
+
const fn = registry[ref.name];
|
|
5633
|
+
if (!fn) continue;
|
|
5634
|
+
const ctx = {
|
|
5635
|
+
formData: data,
|
|
5636
|
+
path: pointer,
|
|
5637
|
+
args: ref.args
|
|
5638
|
+
};
|
|
5639
|
+
out.push({
|
|
5640
|
+
id: `asyncField:${pointer}:${ref.name}`,
|
|
5641
|
+
key: keyOf(value),
|
|
5642
|
+
pointer,
|
|
5643
|
+
run: () => fn(value, ctx)
|
|
5644
|
+
});
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
function collectFormCandidates(schema, data, registry, out) {
|
|
5649
|
+
for (const ref of toRefs(schema["x-ui-validate"])) {
|
|
5650
|
+
const fn = registry[ref.name];
|
|
5651
|
+
if (!fn) continue;
|
|
5652
|
+
const ctx = { formData: data, path: "", args: ref.args };
|
|
5653
|
+
const basis = ref.dependsOn ? ref.dependsOn.map((f) => keyOf(data[f])).join("|") : keyOf(data);
|
|
5654
|
+
out.push({
|
|
5655
|
+
id: `asyncForm:${ref.name}`,
|
|
5656
|
+
key: basis,
|
|
5657
|
+
pointer: "",
|
|
5658
|
+
run: () => fn(data, ctx)
|
|
5659
|
+
});
|
|
5660
|
+
}
|
|
5661
|
+
}
|
|
5662
|
+
function collectCandidates(schema, data, registry, uiSchema, isRoot) {
|
|
5663
|
+
const out = [];
|
|
5664
|
+
if (registry.asyncField) {
|
|
5665
|
+
collectFieldCandidates(schema, data, registry.asyncField, uiSchema, out);
|
|
5666
|
+
}
|
|
5667
|
+
if (registry.asyncForm && isRoot) {
|
|
5668
|
+
collectFormCandidates(schema, data, registry.asyncForm, out);
|
|
5669
|
+
}
|
|
5670
|
+
return out;
|
|
5671
|
+
}
|
|
5672
|
+
function useAsyncValidators(schema, data, registry, uiSchema, isRoot) {
|
|
5673
|
+
const [entries, setEntries] = useState16({});
|
|
5674
|
+
const handledKey = useRef7({});
|
|
5675
|
+
const candidates = registry && (registry.asyncField || registry.asyncForm) ? collectCandidates(schema, data, registry, uiSchema, isRoot) : [];
|
|
5676
|
+
const liveIds = new Set(candidates.map((c) => c.id));
|
|
5677
|
+
const signature = candidates.map((c) => `${c.id}=${c.key}`).join(";");
|
|
5678
|
+
useEffect7(() => {
|
|
5679
|
+
for (const c of candidates) {
|
|
5680
|
+
if (handledKey.current[c.id] === c.key) continue;
|
|
5681
|
+
handledKey.current[c.id] = c.key;
|
|
5682
|
+
setEntries((e) => ({
|
|
5683
|
+
...e,
|
|
5684
|
+
[c.id]: { key: c.key, pending: true, errors: [] }
|
|
5685
|
+
}));
|
|
5686
|
+
const settle = (errors) => {
|
|
5687
|
+
if (handledKey.current[c.id] !== c.key) return;
|
|
5688
|
+
setEntries((e) => ({
|
|
5689
|
+
...e,
|
|
5690
|
+
[c.id]: { key: c.key, pending: false, errors }
|
|
5691
|
+
}));
|
|
5692
|
+
};
|
|
5693
|
+
c.run().then((r) => settle(toFieldErrors(r, c.pointer))).catch(() => settle([]));
|
|
5694
|
+
}
|
|
5695
|
+
}, [signature]);
|
|
5696
|
+
const liveKey = [...liveIds].sort().join(",");
|
|
5697
|
+
useEffect7(() => {
|
|
5698
|
+
setEntries((e) => {
|
|
5699
|
+
const kept = Object.entries(e).filter(([id]) => liveIds.has(id));
|
|
5700
|
+
return kept.length === Object.keys(e).length ? e : Object.fromEntries(kept);
|
|
5701
|
+
});
|
|
5702
|
+
for (const id of Object.keys(handledKey.current)) {
|
|
5703
|
+
if (!liveIds.has(id)) delete handledKey.current[id];
|
|
5704
|
+
}
|
|
5705
|
+
}, [liveKey]);
|
|
5706
|
+
let isValidating = false;
|
|
5707
|
+
const errorList = [];
|
|
5708
|
+
for (const c of candidates) {
|
|
5709
|
+
const entry = entries[c.id];
|
|
5710
|
+
if (!entry || entry.key !== c.key || entry.pending) {
|
|
5711
|
+
isValidating = true;
|
|
5712
|
+
continue;
|
|
5713
|
+
}
|
|
5714
|
+
errorList.push(...entry.errors);
|
|
5715
|
+
}
|
|
5716
|
+
const errorSig = errorList.map((e) => `${e.pointer}\0${e.message}`).join("|");
|
|
5717
|
+
const asyncErrors = useMemo10(() => errorList, [errorSig]);
|
|
5718
|
+
return { asyncErrors, isValidating };
|
|
5719
|
+
}
|
|
5720
|
+
|
|
3487
5721
|
// src/hooks/orderProperties.ts
|
|
3488
5722
|
function orderProperties(properties, orderArray) {
|
|
3489
5723
|
const allEntries = Object.entries(properties);
|
|
@@ -3500,9 +5734,14 @@ function orderProperties(properties, orderArray) {
|
|
|
3500
5734
|
}
|
|
3501
5735
|
|
|
3502
5736
|
// src/hooks/useFormEngine.ts
|
|
5737
|
+
var COMMITTED_FORMATS = /* @__PURE__ */ new Set(["date", "time", "date-time"]);
|
|
5738
|
+
function isCommittedFormat(property) {
|
|
5739
|
+
return property.format !== void 0 && COMMITTED_FORMATS.has(property.format);
|
|
5740
|
+
}
|
|
3503
5741
|
function isImmediateField(fieldName, properties) {
|
|
3504
5742
|
const property = properties[fieldName];
|
|
3505
|
-
|
|
5743
|
+
if (!property) return false;
|
|
5744
|
+
return property.type === "boolean" || isNullableBoolean3(property) || isCommittedFormat(property) || property.enum !== void 0 || property.items?.enum !== void 0 || property["x-ui-options"]?.immediate === true;
|
|
3506
5745
|
}
|
|
3507
5746
|
function computeBaselineUpdate(newData, originalData, initialized, schemaPropertyNames) {
|
|
3508
5747
|
const newCount = Object.keys(newData).length;
|
|
@@ -3527,6 +5766,11 @@ function computeBaselineUpdate(newData, originalData, initialized, schemaPropert
|
|
|
3527
5766
|
}
|
|
3528
5767
|
return null;
|
|
3529
5768
|
}
|
|
5769
|
+
function computeAllErrors(args) {
|
|
5770
|
+
const { schema, data, customSync, asyncErrors, extraErrors } = args;
|
|
5771
|
+
const base = validateFormData(schema, stripFormItemIds(data)).fieldErrors;
|
|
5772
|
+
return mergeFieldErrors(base, customSync, asyncErrors, extraErrors ?? []);
|
|
5773
|
+
}
|
|
3530
5774
|
function useFormEngine({
|
|
3531
5775
|
schema,
|
|
3532
5776
|
data,
|
|
@@ -3541,51 +5785,54 @@ function useFormEngine({
|
|
|
3541
5785
|
deferChanges = false,
|
|
3542
5786
|
deferStateUpdates = false,
|
|
3543
5787
|
allowPristineSave = false,
|
|
3544
|
-
refResolver
|
|
5788
|
+
refResolver,
|
|
5789
|
+
extraErrors,
|
|
5790
|
+
validators,
|
|
5791
|
+
isRoot = true
|
|
3545
5792
|
}) {
|
|
3546
|
-
const refResolvedSchema =
|
|
5793
|
+
const refResolvedSchema = useMemo11(
|
|
3547
5794
|
() => resolveRefs(schema, refResolver),
|
|
3548
5795
|
[schema, refResolver]
|
|
3549
5796
|
);
|
|
3550
|
-
const seed =
|
|
5797
|
+
const seed = useState17(
|
|
3551
5798
|
() => ensureFormItemIds(
|
|
3552
5799
|
applySchemaDefaults2(data, refResolvedSchema),
|
|
3553
5800
|
refResolvedSchema
|
|
3554
5801
|
)
|
|
3555
5802
|
)[0];
|
|
3556
|
-
const [originalData, setOriginalData] =
|
|
5803
|
+
const [originalData, setOriginalData] = useState17(
|
|
3557
5804
|
() => seed
|
|
3558
5805
|
);
|
|
3559
|
-
const [originalDataInitialized, setOriginalDataInitialized] =
|
|
3560
|
-
const [nonSchemaData, setNonSchemaData] =
|
|
5806
|
+
const [originalDataInitialized, setOriginalDataInitialized] = useState17(false);
|
|
5807
|
+
const [nonSchemaData, setNonSchemaData] = useState17(
|
|
3561
5808
|
() => {
|
|
3562
|
-
const props =
|
|
5809
|
+
const props = getSchemaProperties4(refResolvedSchema);
|
|
3563
5810
|
const names = Object.keys(props);
|
|
3564
5811
|
return Object.fromEntries(
|
|
3565
5812
|
Object.entries(data).filter(([k]) => !names.includes(k))
|
|
3566
5813
|
);
|
|
3567
5814
|
}
|
|
3568
5815
|
);
|
|
3569
|
-
const [formData, setFormData] =
|
|
3570
|
-
const formDataRef =
|
|
5816
|
+
const [formData, setFormData] = useState17(() => seed);
|
|
5817
|
+
const formDataRef = useRef8(formData);
|
|
3571
5818
|
formDataRef.current = formData;
|
|
3572
|
-
const [pendingChanges, setPendingChanges] =
|
|
3573
|
-
const resolvedSchema =
|
|
5819
|
+
const [pendingChanges, setPendingChanges] = useState17({});
|
|
5820
|
+
const resolvedSchema = useMemo11(
|
|
3574
5821
|
() => resolveSchema(refResolvedSchema, formData),
|
|
3575
5822
|
[refResolvedSchema, formData]
|
|
3576
5823
|
);
|
|
3577
|
-
const orderedProperties =
|
|
3578
|
-
const properties2 =
|
|
5824
|
+
const orderedProperties = useMemo11(() => {
|
|
5825
|
+
const properties2 = getSchemaProperties4(resolvedSchema);
|
|
3579
5826
|
const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
|
|
3580
5827
|
const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
|
|
3581
5828
|
return orderProperties(properties2, orderArray);
|
|
3582
5829
|
}, [resolvedSchema, uiSchema]);
|
|
3583
|
-
const [initialDefaultsSent, setInitialDefaultsSent] =
|
|
3584
|
-
const
|
|
3585
|
-
const [touched, setTouched] =
|
|
3586
|
-
const [submitAttempted, setSubmitAttempted] =
|
|
3587
|
-
const properties =
|
|
3588
|
-
|
|
5830
|
+
const [initialDefaultsSent, setInitialDefaultsSent] = useState17(false);
|
|
5831
|
+
const edited = useRef8(/* @__PURE__ */ new Set());
|
|
5832
|
+
const [touched, setTouched] = useState17({});
|
|
5833
|
+
const [submitAttempted, setSubmitAttempted] = useState17(false);
|
|
5834
|
+
const properties = getSchemaProperties4(resolvedSchema);
|
|
5835
|
+
useEffect8(() => {
|
|
3589
5836
|
if (!initialDefaultsSent) {
|
|
3590
5837
|
const hasDefaults = Object.entries(properties).some(
|
|
3591
5838
|
([fieldName, property]) => data[fieldName] === void 0 && getFieldDefault(property) !== void 0
|
|
@@ -3596,7 +5843,7 @@ function useFormEngine({
|
|
|
3596
5843
|
setInitialDefaultsSent(true);
|
|
3597
5844
|
}
|
|
3598
5845
|
}, [initialDefaultsSent, properties, data, formData, onChange]);
|
|
3599
|
-
|
|
5846
|
+
useEffect8(() => {
|
|
3600
5847
|
const currentPropertyNames = Object.keys(properties);
|
|
3601
5848
|
const keysToRemove = Object.keys(formData).filter(
|
|
3602
5849
|
(k) => !currentPropertyNames.includes(k)
|
|
@@ -3610,8 +5857,23 @@ function useFormEngine({
|
|
|
3610
5857
|
if (onChange) onChange(cleanedData);
|
|
3611
5858
|
}
|
|
3612
5859
|
}, [properties, formData, onChange]);
|
|
3613
|
-
const
|
|
3614
|
-
|
|
5860
|
+
const shownFields = useRef8(
|
|
5861
|
+
new Set(Object.keys(getSchemaProperties4(refResolvedSchema)))
|
|
5862
|
+
);
|
|
5863
|
+
useEffect8(() => {
|
|
5864
|
+
const names = Object.keys(properties);
|
|
5865
|
+
const appeared = names.filter((n) => !shownFields.current.has(n));
|
|
5866
|
+
shownFields.current = new Set(names);
|
|
5867
|
+
if (appeared.length === 0) return;
|
|
5868
|
+
const filled = applySchemaDefaults2(formData, {
|
|
5869
|
+
properties: Object.fromEntries(appeared.map((n) => [n, properties[n]]))
|
|
5870
|
+
});
|
|
5871
|
+
if (isEqual(filled, formData)) return;
|
|
5872
|
+
setFormData(filled);
|
|
5873
|
+
if (onChange) onChange(filled);
|
|
5874
|
+
}, [properties, formData, onChange]);
|
|
5875
|
+
const prevDataRef = useRef8(data);
|
|
5876
|
+
useEffect8(() => {
|
|
3615
5877
|
if (isEqual(prevDataRef.current, data)) return;
|
|
3616
5878
|
prevDataRef.current = data;
|
|
3617
5879
|
const newData = ensureFormItemIds(
|
|
@@ -3641,32 +5903,51 @@ function useFormEngine({
|
|
|
3641
5903
|
originalData,
|
|
3642
5904
|
originalDataInitialized
|
|
3643
5905
|
]);
|
|
3644
|
-
const hasChanges =
|
|
5906
|
+
const hasChanges = useMemo11(
|
|
3645
5907
|
() => !isEqual(formData, originalData),
|
|
3646
5908
|
[formData, originalData]
|
|
3647
5909
|
);
|
|
3648
|
-
const displayData =
|
|
5910
|
+
const displayData = useMemo11(() => {
|
|
3649
5911
|
if (deferStateUpdates && Object.keys(pendingChanges).length > 0) {
|
|
3650
5912
|
return { ...formData, ...pendingChanges };
|
|
3651
5913
|
}
|
|
3652
5914
|
return formData;
|
|
3653
5915
|
}, [formData, pendingChanges, deferStateUpdates]);
|
|
3654
|
-
const
|
|
3655
|
-
() =>
|
|
3656
|
-
[resolvedSchema, displayData]
|
|
5916
|
+
const customSync = useMemo11(
|
|
5917
|
+
() => runValidators(resolvedSchema, displayData, validators, uiSchema, isRoot),
|
|
5918
|
+
[resolvedSchema, displayData, validators, uiSchema, isRoot]
|
|
5919
|
+
);
|
|
5920
|
+
const { asyncErrors, isValidating } = useAsyncValidators(
|
|
5921
|
+
resolvedSchema,
|
|
5922
|
+
displayData,
|
|
5923
|
+
validators,
|
|
5924
|
+
uiSchema,
|
|
5925
|
+
isRoot
|
|
3657
5926
|
);
|
|
5927
|
+
const validation = useMemo11(() => {
|
|
5928
|
+
const merged = computeAllErrors({
|
|
5929
|
+
schema: resolvedSchema,
|
|
5930
|
+
data: displayData,
|
|
5931
|
+
customSync,
|
|
5932
|
+
asyncErrors,
|
|
5933
|
+
extraErrors
|
|
5934
|
+
});
|
|
5935
|
+
return {
|
|
5936
|
+
fieldErrors: merged,
|
|
5937
|
+
errors: merged.map((e) => e.message),
|
|
5938
|
+
isValid: merged.length === 0
|
|
5939
|
+
};
|
|
5940
|
+
}, [resolvedSchema, displayData, customSync, asyncErrors, extraErrors]);
|
|
3658
5941
|
const { isValid, fieldErrors } = validation;
|
|
3659
|
-
const onValidationChangeRef =
|
|
5942
|
+
const onValidationChangeRef = useRef8(onValidationChange);
|
|
3660
5943
|
onValidationChangeRef.current = onValidationChange;
|
|
3661
|
-
|
|
5944
|
+
useEffect8(() => {
|
|
3662
5945
|
onValidationChangeRef.current?.(validation.errors, validation.isValid);
|
|
3663
5946
|
}, [validation]);
|
|
3664
|
-
const handleFieldChange =
|
|
5947
|
+
const handleFieldChange = useCallback8(
|
|
3665
5948
|
(fieldName, value) => {
|
|
3666
5949
|
if (readOnly) return;
|
|
3667
|
-
|
|
3668
|
-
(prev) => prev[fieldName] ? prev : { ...prev, [fieldName]: true }
|
|
3669
|
-
);
|
|
5950
|
+
edited.current.add(fieldName);
|
|
3670
5951
|
const immediate = isImmediateField(fieldName, properties);
|
|
3671
5952
|
if (deferStateUpdates && !immediate) {
|
|
3672
5953
|
setPendingChanges((prev) => ({ ...prev, [fieldName]: value }));
|
|
@@ -3689,7 +5970,7 @@ function useFormEngine({
|
|
|
3689
5970
|
properties
|
|
3690
5971
|
]
|
|
3691
5972
|
);
|
|
3692
|
-
const flushPendingField =
|
|
5973
|
+
const flushPendingField = useCallback8(
|
|
3693
5974
|
(fieldName) => {
|
|
3694
5975
|
if (!deferStateUpdates) return;
|
|
3695
5976
|
if (pendingChanges[fieldName] === void 0) return;
|
|
@@ -3704,10 +5985,10 @@ function useFormEngine({
|
|
|
3704
5985
|
},
|
|
3705
5986
|
[showActions, deferStateUpdates, pendingChanges, formData, onChange]
|
|
3706
5987
|
);
|
|
3707
|
-
const handleFieldBlur =
|
|
5988
|
+
const handleFieldBlur = useCallback8(
|
|
3708
5989
|
(fieldName) => {
|
|
3709
5990
|
if (readOnly) return;
|
|
3710
|
-
if (edited
|
|
5991
|
+
if (edited.current.has(fieldName)) {
|
|
3711
5992
|
setTouched(
|
|
3712
5993
|
(prev) => prev[fieldName] ? prev : { ...prev, [fieldName]: true }
|
|
3713
5994
|
);
|
|
@@ -3725,7 +6006,6 @@ function useFormEngine({
|
|
|
3725
6006
|
},
|
|
3726
6007
|
[
|
|
3727
6008
|
readOnly,
|
|
3728
|
-
edited,
|
|
3729
6009
|
pendingChanges,
|
|
3730
6010
|
deferChanges,
|
|
3731
6011
|
deferStateUpdates,
|
|
@@ -3735,13 +6015,26 @@ function useFormEngine({
|
|
|
3735
6015
|
flushPendingField
|
|
3736
6016
|
]
|
|
3737
6017
|
);
|
|
3738
|
-
const handleSave =
|
|
6018
|
+
const handleSave = useCallback8(() => {
|
|
3739
6019
|
setSubmitAttempted(true);
|
|
3740
6020
|
if (!hasChanges && !allowPristineSave) return;
|
|
6021
|
+
if (isValidating) return;
|
|
3741
6022
|
const finalData = deferStateUpdates && Object.keys(pendingChanges).length > 0 ? { ...formData, ...pendingChanges } : formData;
|
|
3742
6023
|
const completeData = { ...nonSchemaData, ...finalData };
|
|
3743
|
-
const
|
|
3744
|
-
|
|
6024
|
+
const saveErrors = computeAllErrors({
|
|
6025
|
+
schema: resolvedSchema,
|
|
6026
|
+
data: finalData,
|
|
6027
|
+
customSync: runValidators(
|
|
6028
|
+
resolvedSchema,
|
|
6029
|
+
finalData,
|
|
6030
|
+
validators,
|
|
6031
|
+
uiSchema,
|
|
6032
|
+
isRoot
|
|
6033
|
+
),
|
|
6034
|
+
asyncErrors,
|
|
6035
|
+
extraErrors
|
|
6036
|
+
});
|
|
6037
|
+
if (saveErrors.length === 0 && onUpdate) {
|
|
3745
6038
|
const changed = {};
|
|
3746
6039
|
for (const key of Object.keys(finalData)) {
|
|
3747
6040
|
if (!isEqual(finalData[key], originalData[key])) {
|
|
@@ -3761,12 +6054,18 @@ function useFormEngine({
|
|
|
3761
6054
|
pendingChanges,
|
|
3762
6055
|
deferStateUpdates,
|
|
3763
6056
|
nonSchemaData,
|
|
3764
|
-
onUpdate
|
|
6057
|
+
onUpdate,
|
|
6058
|
+
extraErrors,
|
|
6059
|
+
asyncErrors,
|
|
6060
|
+
isValidating,
|
|
6061
|
+
uiSchema,
|
|
6062
|
+
validators,
|
|
6063
|
+
isRoot
|
|
3765
6064
|
]);
|
|
3766
|
-
const handleCancel =
|
|
6065
|
+
const handleCancel = useCallback8(() => {
|
|
3767
6066
|
setFormData({ ...originalData });
|
|
3768
6067
|
setPendingChanges({});
|
|
3769
|
-
|
|
6068
|
+
edited.current = /* @__PURE__ */ new Set();
|
|
3770
6069
|
setTouched({});
|
|
3771
6070
|
setSubmitAttempted(false);
|
|
3772
6071
|
onCancel?.();
|
|
@@ -3778,6 +6077,7 @@ function useFormEngine({
|
|
|
3778
6077
|
orderedProperties,
|
|
3779
6078
|
hasChanges,
|
|
3780
6079
|
isValid,
|
|
6080
|
+
isValidating,
|
|
3781
6081
|
fieldErrors,
|
|
3782
6082
|
touched,
|
|
3783
6083
|
submitAttempted,
|
|
@@ -3793,30 +6093,7 @@ import {
|
|
|
3793
6093
|
useValidationDisplay,
|
|
3794
6094
|
useSaveWhenInvalid
|
|
3795
6095
|
} from "@petrarca/sonnet-core/config";
|
|
3796
|
-
import { jsx as
|
|
3797
|
-
function stripFids(data) {
|
|
3798
|
-
const result = {};
|
|
3799
|
-
for (const [key, value] of Object.entries(data)) {
|
|
3800
|
-
if (Array.isArray(value)) {
|
|
3801
|
-
result[key] = value.map((item) => {
|
|
3802
|
-
if (item !== null && typeof item === "object" && !Array.isArray(item)) {
|
|
3803
|
-
const rest = Object.fromEntries(
|
|
3804
|
-
Object.entries(item).filter(
|
|
3805
|
-
([k]) => k !== FORM_ITEM_ID_FIELD2
|
|
3806
|
-
)
|
|
3807
|
-
);
|
|
3808
|
-
return stripFids(rest);
|
|
3809
|
-
}
|
|
3810
|
-
return item;
|
|
3811
|
-
});
|
|
3812
|
-
} else if (value !== null && typeof value === "object") {
|
|
3813
|
-
result[key] = stripFids(value);
|
|
3814
|
-
} else {
|
|
3815
|
-
result[key] = value;
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
return result;
|
|
3819
|
-
}
|
|
6096
|
+
import { jsx as jsx48, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
3820
6097
|
function resolveFlagDefaults(props) {
|
|
3821
6098
|
return {
|
|
3822
6099
|
disabled: props.disabled ?? false,
|
|
@@ -3838,18 +6115,20 @@ function resolveValueDefaults(props) {
|
|
|
3838
6115
|
uiSchema: props.uiSchema ?? {}
|
|
3839
6116
|
};
|
|
3840
6117
|
}
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
6118
|
+
var keepFids = (data) => data;
|
|
6119
|
+
function useWrappedCallbacks(onChange, onBlur, onUpdate, isRootForm) {
|
|
6120
|
+
const strip = isRootForm ? stripFormItemIds2 : keepFids;
|
|
6121
|
+
const wrappedOnChange = useCallback9(
|
|
6122
|
+
(d) => onChange?.(strip(d)),
|
|
6123
|
+
[onChange, strip]
|
|
3845
6124
|
);
|
|
3846
|
-
const wrappedOnBlur =
|
|
3847
|
-
(fieldName, d) => onBlur?.(fieldName,
|
|
3848
|
-
[onBlur]
|
|
6125
|
+
const wrappedOnBlur = useCallback9(
|
|
6126
|
+
(fieldName, d) => onBlur?.(fieldName, strip(d)),
|
|
6127
|
+
[onBlur, strip]
|
|
3849
6128
|
);
|
|
3850
|
-
const wrappedOnUpdate =
|
|
3851
|
-
(d, changed, diff) => onUpdate?.(
|
|
3852
|
-
[onUpdate]
|
|
6129
|
+
const wrappedOnUpdate = useCallback9(
|
|
6130
|
+
(d, changed, diff) => onUpdate?.(strip(d), strip(changed), diff),
|
|
6131
|
+
[onUpdate, strip]
|
|
3853
6132
|
);
|
|
3854
6133
|
return {
|
|
3855
6134
|
wrappedOnChange: onChange ? wrappedOnChange : void 0,
|
|
@@ -3860,7 +6139,7 @@ function useWrappedCallbacks(onChange, onBlur, onUpdate) {
|
|
|
3860
6139
|
function buildFieldMap(fields) {
|
|
3861
6140
|
const map = /* @__PURE__ */ new Map();
|
|
3862
6141
|
for (const field of fields) {
|
|
3863
|
-
if (
|
|
6142
|
+
if (React18.isValidElement(field) && field.key != null) {
|
|
3864
6143
|
map.set(String(field.key), field);
|
|
3865
6144
|
}
|
|
3866
6145
|
}
|
|
@@ -3879,12 +6158,12 @@ function resolveGridColumns(row, fieldCount) {
|
|
|
3879
6158
|
}
|
|
3880
6159
|
function wrapInGroup(group, key, content) {
|
|
3881
6160
|
if (!group) return content;
|
|
3882
|
-
return /* @__PURE__ */
|
|
6161
|
+
return /* @__PURE__ */ jsxs31(
|
|
3883
6162
|
"fieldset",
|
|
3884
6163
|
{
|
|
3885
6164
|
className: "rounded-md border border-border/60 px-3 pb-3 pt-1",
|
|
3886
6165
|
children: [
|
|
3887
|
-
/* @__PURE__ */
|
|
6166
|
+
/* @__PURE__ */ jsx48("legend", { className: "px-1 text-sm font-medium text-muted-foreground", children: group }),
|
|
3888
6167
|
content
|
|
3889
6168
|
]
|
|
3890
6169
|
},
|
|
@@ -3906,7 +6185,7 @@ function buildRowElement(row, fieldMap, defaultRowGap) {
|
|
|
3906
6185
|
gridTemplateColumns: resolveGridColumns(row, rowFields.length),
|
|
3907
6186
|
gap: `${rowGap}px`
|
|
3908
6187
|
};
|
|
3909
|
-
const inner = /* @__PURE__ */
|
|
6188
|
+
const inner = /* @__PURE__ */ jsx48("div", { style, children: rowFields }, `row:${rowKey}`);
|
|
3910
6189
|
return {
|
|
3911
6190
|
element: wrapInGroup(row.group, rowKey, inner),
|
|
3912
6191
|
placed: row.fields
|
|
@@ -3915,7 +6194,7 @@ function buildRowElement(row, fieldMap, defaultRowGap) {
|
|
|
3915
6194
|
function collectUnplacedFields(fields, placed) {
|
|
3916
6195
|
const result = [];
|
|
3917
6196
|
for (const field of fields) {
|
|
3918
|
-
if (
|
|
6197
|
+
if (React18.isValidElement(field) && field.key != null && !placed.has(String(field.key))) {
|
|
3919
6198
|
result.push(field);
|
|
3920
6199
|
}
|
|
3921
6200
|
}
|
|
@@ -3940,8 +6219,11 @@ function layoutFieldsInRows(fields, rowDefs, defaultRowGap) {
|
|
|
3940
6219
|
);
|
|
3941
6220
|
return elements.concat(collectUnplacedFields(fields, placed));
|
|
3942
6221
|
}
|
|
3943
|
-
function
|
|
3944
|
-
return
|
|
6222
|
+
function rootFormData(nestedContext, ownData) {
|
|
6223
|
+
return nestedContext?.rootData ?? ownData;
|
|
6224
|
+
}
|
|
6225
|
+
function rootSubmitAttempted(nestedContext, ownSubmitAttempted) {
|
|
6226
|
+
return ownSubmitAttempted || (nestedContext?.submitAttempted ?? false);
|
|
3945
6227
|
}
|
|
3946
6228
|
function blurHandlerNeeded({
|
|
3947
6229
|
deferStateUpdates,
|
|
@@ -3960,6 +6242,19 @@ function pointerRoot(pointer) {
|
|
|
3960
6242
|
const token = pointer.split("/")[1] ?? "";
|
|
3961
6243
|
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
3962
6244
|
}
|
|
6245
|
+
function fieldRawErrors(fieldName, fieldErrors, extraErrors, gated) {
|
|
6246
|
+
const forField = fieldErrors.filter(
|
|
6247
|
+
(e) => pointerRoot(e.pointer) === fieldName
|
|
6248
|
+
);
|
|
6249
|
+
if (forField.length === 0) return void 0;
|
|
6250
|
+
const extraMessages = new Set(
|
|
6251
|
+
(extraErrors ?? []).filter((e) => pointerRoot(e.pointer) === fieldName).map((e) => e.message)
|
|
6252
|
+
);
|
|
6253
|
+
const always = forField.filter((e) => extraMessages.has(e.message)).map((e) => e.message);
|
|
6254
|
+
const rest = gated ? forField.filter((e) => !extraMessages.has(e.message)).map((e) => e.message) : [];
|
|
6255
|
+
const all = [...always, ...rest];
|
|
6256
|
+
return all.length > 0 ? all : void 0;
|
|
6257
|
+
}
|
|
3963
6258
|
function groupErrorCount(tab, fieldErrors) {
|
|
3964
6259
|
if (fieldErrors.length === 0) return 0;
|
|
3965
6260
|
const owned = new Set(tab.rows.flatMap((row) => row.fields));
|
|
@@ -3992,11 +6287,28 @@ function HiddenErrorNotice({
|
|
|
3992
6287
|
count
|
|
3993
6288
|
}) {
|
|
3994
6289
|
if (count === 0) return null;
|
|
3995
|
-
return /* @__PURE__ */
|
|
3996
|
-
/* @__PURE__ */
|
|
6290
|
+
return /* @__PURE__ */ jsxs31("p", { className: "flex items-center gap-1.5 text-sm text-destructive", children: [
|
|
6291
|
+
/* @__PURE__ */ jsx48(AlertCircle2, { className: "h-4 w-4 shrink-0" }),
|
|
3997
6292
|
count === 1 ? "1 required field is in a section that is not currently shown." : `${count} required fields are in sections that are not currently shown.`
|
|
3998
6293
|
] });
|
|
3999
6294
|
}
|
|
6295
|
+
function FormLevelErrors({
|
|
6296
|
+
messages
|
|
6297
|
+
}) {
|
|
6298
|
+
if (messages.length === 0) return null;
|
|
6299
|
+
return /* @__PURE__ */ jsx48("div", { className: "space-y-1", children: messages.map((msg, i) => /* @__PURE__ */ jsxs31(
|
|
6300
|
+
"p",
|
|
6301
|
+
{
|
|
6302
|
+
role: "alert",
|
|
6303
|
+
className: "flex items-center gap-1.5 text-sm text-destructive",
|
|
6304
|
+
children: [
|
|
6305
|
+
/* @__PURE__ */ jsx48(AlertCircle2, { className: "h-4 w-4 shrink-0" }),
|
|
6306
|
+
msg
|
|
6307
|
+
]
|
|
6308
|
+
},
|
|
6309
|
+
i
|
|
6310
|
+
)) });
|
|
6311
|
+
}
|
|
4000
6312
|
function layoutFieldsInGroups(fields, groupDefs, defaultRowGap, fieldErrors, formData, GroupTemplate) {
|
|
4001
6313
|
const fieldMap = buildFieldMap(fields);
|
|
4002
6314
|
const placed = /* @__PURE__ */ new Set();
|
|
@@ -4015,7 +6327,7 @@ function layoutFieldsInGroups(fields, groupDefs, defaultRowGap, fieldErrors, for
|
|
|
4015
6327
|
content: b.rows,
|
|
4016
6328
|
errorCount: groupErrorCount(groupDefs[b.index], fieldErrors)
|
|
4017
6329
|
}));
|
|
4018
|
-
return [/* @__PURE__ */
|
|
6330
|
+
return [/* @__PURE__ */ jsx48(GroupTemplate, { groups }, "form:groups"), ...unplaced];
|
|
4019
6331
|
}
|
|
4020
6332
|
function FormContainer({
|
|
4021
6333
|
displayContents,
|
|
@@ -4028,22 +6340,25 @@ function FormContainer({
|
|
|
4028
6340
|
disabled,
|
|
4029
6341
|
hasChanges,
|
|
4030
6342
|
isValid,
|
|
6343
|
+
isValidating,
|
|
4031
6344
|
saveWhenInvalid,
|
|
4032
6345
|
saveLabel,
|
|
4033
6346
|
cancelLabel,
|
|
4034
6347
|
showCancel,
|
|
4035
6348
|
leadingActions,
|
|
4036
6349
|
hiddenErrorCount,
|
|
6350
|
+
formLevelErrors,
|
|
4037
6351
|
children
|
|
4038
6352
|
}) {
|
|
4039
6353
|
if (displayContents) {
|
|
4040
|
-
return /* @__PURE__ */
|
|
6354
|
+
return /* @__PURE__ */ jsx48("div", { style: { display: "contents" }, children });
|
|
4041
6355
|
}
|
|
4042
|
-
return /* @__PURE__ */
|
|
6356
|
+
return /* @__PURE__ */ jsxs31(SimpleStack, { gap: "md", children: [
|
|
4043
6357
|
children,
|
|
4044
|
-
showExtraProperties && /* @__PURE__ */
|
|
4045
|
-
/* @__PURE__ */
|
|
4046
|
-
|
|
6358
|
+
showExtraProperties && /* @__PURE__ */ jsx48(ExtraPropertiesCard_default, { properties: formData, schema: resolvedSchema }),
|
|
6359
|
+
/* @__PURE__ */ jsx48(HiddenErrorNotice, { count: hiddenErrorCount }),
|
|
6360
|
+
/* @__PURE__ */ jsx48(FormLevelErrors, { messages: formLevelErrors }),
|
|
6361
|
+
showActions && /* @__PURE__ */ jsx48(
|
|
4047
6362
|
FormActions,
|
|
4048
6363
|
{
|
|
4049
6364
|
onSave: handleSave,
|
|
@@ -4051,6 +6366,7 @@ function FormContainer({
|
|
|
4051
6366
|
disabled,
|
|
4052
6367
|
hasChanges,
|
|
4053
6368
|
isValid,
|
|
6369
|
+
isValidating,
|
|
4054
6370
|
saveWhenInvalid,
|
|
4055
6371
|
saveLabel,
|
|
4056
6372
|
cancelLabel,
|
|
@@ -4070,9 +6386,12 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4070
6386
|
onUpdate,
|
|
4071
6387
|
onCancel,
|
|
4072
6388
|
templates,
|
|
6389
|
+
validators,
|
|
4073
6390
|
leadingActions,
|
|
4074
6391
|
idPrefix = "field"
|
|
4075
6392
|
} = props;
|
|
6393
|
+
const nestedFormContext = useContext2(NestedFormContext);
|
|
6394
|
+
const isRootForm = nestedFormContext === null;
|
|
4076
6395
|
const {
|
|
4077
6396
|
disabled,
|
|
4078
6397
|
readOnly,
|
|
@@ -4085,7 +6404,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4085
6404
|
allowPristineSave
|
|
4086
6405
|
} = resolveFlagDefaults(props);
|
|
4087
6406
|
const { saveLabel, cancelLabel, widgets, uiSchema } = resolveValueDefaults(props);
|
|
4088
|
-
const { wrappedOnChange, wrappedOnBlur, wrappedOnUpdate } = useWrappedCallbacks(onChange, onBlur, onUpdate);
|
|
6407
|
+
const { wrappedOnChange, wrappedOnBlur, wrappedOnUpdate } = useWrappedCallbacks(onChange, onBlur, onUpdate, isRootForm);
|
|
4089
6408
|
const {
|
|
4090
6409
|
formData,
|
|
4091
6410
|
displayData,
|
|
@@ -4093,6 +6412,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4093
6412
|
orderedProperties,
|
|
4094
6413
|
hasChanges,
|
|
4095
6414
|
isValid,
|
|
6415
|
+
isValidating,
|
|
4096
6416
|
fieldErrors,
|
|
4097
6417
|
touched,
|
|
4098
6418
|
submitAttempted,
|
|
@@ -4114,40 +6434,49 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4114
6434
|
deferChanges,
|
|
4115
6435
|
deferStateUpdates,
|
|
4116
6436
|
allowPristineSave,
|
|
4117
|
-
refResolver: props.refResolver
|
|
6437
|
+
refResolver: props.refResolver,
|
|
6438
|
+
extraErrors: props.extraErrors,
|
|
6439
|
+
validators,
|
|
6440
|
+
isRoot: isRootForm
|
|
4118
6441
|
});
|
|
4119
6442
|
const layoutLabelPlacement = uiSchema["x-ui-layout"]?.labelPlacement;
|
|
4120
|
-
const globalOptions =
|
|
6443
|
+
const globalOptions = useMemo12(
|
|
4121
6444
|
() => ({
|
|
4122
6445
|
...layoutLabelPlacement ? { labelPlacement: layoutLabelPlacement } : {},
|
|
4123
6446
|
...uiSchema["x-ui-globalOptions"] || {}
|
|
4124
6447
|
}),
|
|
4125
6448
|
[layoutLabelPlacement, uiSchema]
|
|
4126
6449
|
);
|
|
4127
|
-
const resolvedTemplates =
|
|
6450
|
+
const resolvedTemplates = useMemo12(
|
|
4128
6451
|
() => ({ ...DEFAULT_TEMPLATES, ...templates }),
|
|
4129
6452
|
[templates]
|
|
4130
6453
|
);
|
|
4131
|
-
const nestedRenderer =
|
|
4132
|
-
(props2) => /* @__PURE__ */
|
|
6454
|
+
const nestedRenderer = useCallback9(
|
|
6455
|
+
(props2) => /* @__PURE__ */ jsx48(
|
|
4133
6456
|
JsonSchemaFormRenderer,
|
|
4134
6457
|
{
|
|
4135
6458
|
...props2,
|
|
4136
6459
|
templates: resolvedTemplates,
|
|
6460
|
+
validators,
|
|
4137
6461
|
showActions: false,
|
|
4138
6462
|
showExtraProperties: false
|
|
4139
6463
|
}
|
|
4140
6464
|
),
|
|
4141
|
-
[resolvedTemplates]
|
|
6465
|
+
[resolvedTemplates, validators]
|
|
4142
6466
|
);
|
|
4143
|
-
const rootData =
|
|
4144
|
-
const
|
|
6467
|
+
const rootData = rootFormData(nestedFormContext, displayData);
|
|
6468
|
+
const effectiveSubmitAttempted = rootSubmitAttempted(
|
|
6469
|
+
nestedFormContext,
|
|
6470
|
+
submitAttempted
|
|
6471
|
+
);
|
|
6472
|
+
const contextValue = useMemo12(
|
|
4145
6473
|
() => ({
|
|
4146
6474
|
renderer: nestedRenderer,
|
|
4147
6475
|
templates: resolvedTemplates,
|
|
4148
|
-
rootData
|
|
6476
|
+
rootData,
|
|
6477
|
+
submitAttempted: effectiveSubmitAttempted
|
|
4149
6478
|
}),
|
|
4150
|
-
[nestedRenderer, resolvedTemplates, rootData]
|
|
6479
|
+
[nestedRenderer, resolvedTemplates, rootData, effectiveSubmitAttempted]
|
|
4151
6480
|
);
|
|
4152
6481
|
const validationDisplay = useValidationDisplay(props.validationDisplay);
|
|
4153
6482
|
const saveWhenInvalid = useSaveWhenInvalid(props.saveWhenInvalid);
|
|
@@ -4157,21 +6486,21 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4157
6486
|
hasOnBlur: !!onBlur,
|
|
4158
6487
|
validationDisplay
|
|
4159
6488
|
});
|
|
4160
|
-
const showFieldErrors =
|
|
6489
|
+
const showFieldErrors = useCallback9(
|
|
4161
6490
|
(fieldName) => fieldErrorsVisible(
|
|
4162
6491
|
validationDisplay,
|
|
4163
|
-
|
|
6492
|
+
effectiveSubmitAttempted,
|
|
4164
6493
|
touched[fieldName]
|
|
4165
6494
|
),
|
|
4166
|
-
[validationDisplay,
|
|
6495
|
+
[validationDisplay, effectiveSubmitAttempted, touched]
|
|
4167
6496
|
);
|
|
4168
|
-
const fields =
|
|
6497
|
+
const fields = useMemo12(
|
|
4169
6498
|
() => orderedProperties.flatMap(([fieldName, property]) => {
|
|
4170
6499
|
const rule = uiSchema[fieldName]?.["x-ui-rule"] ?? property["x-ui-rule"];
|
|
4171
6500
|
const { visible, enabled } = evaluateUiRule(rule, rootData);
|
|
4172
6501
|
if (!visible) return [];
|
|
4173
6502
|
return [
|
|
4174
|
-
/* @__PURE__ */
|
|
6503
|
+
/* @__PURE__ */ jsx48(
|
|
4175
6504
|
FormFieldRenderer,
|
|
4176
6505
|
{
|
|
4177
6506
|
fieldName,
|
|
@@ -4188,7 +6517,12 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4188
6517
|
globalOptions,
|
|
4189
6518
|
formData: displayData,
|
|
4190
6519
|
idPrefix,
|
|
4191
|
-
rawErrors:
|
|
6520
|
+
rawErrors: fieldRawErrors(
|
|
6521
|
+
fieldName,
|
|
6522
|
+
fieldErrors,
|
|
6523
|
+
props.extraErrors,
|
|
6524
|
+
showFieldErrors(fieldName)
|
|
6525
|
+
)
|
|
4192
6526
|
},
|
|
4193
6527
|
fieldName
|
|
4194
6528
|
)
|
|
@@ -4209,6 +6543,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4209
6543
|
needsBlurHandler,
|
|
4210
6544
|
showFieldErrors,
|
|
4211
6545
|
fieldErrors,
|
|
6546
|
+
props.extraErrors,
|
|
4212
6547
|
handleFieldChange,
|
|
4213
6548
|
handleFieldBlur
|
|
4214
6549
|
]
|
|
@@ -4221,7 +6556,7 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4221
6556
|
resolvedTemplates,
|
|
4222
6557
|
layoutConfig?.variant
|
|
4223
6558
|
);
|
|
4224
|
-
const layoutFields =
|
|
6559
|
+
const layoutFields = useMemo12(() => {
|
|
4225
6560
|
if (groupDefs && groupDefs.length > 0) {
|
|
4226
6561
|
return layoutFieldsInGroups(
|
|
4227
6562
|
fields,
|
|
@@ -4246,13 +6581,17 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4246
6581
|
rootData,
|
|
4247
6582
|
groupTemplate
|
|
4248
6583
|
]);
|
|
4249
|
-
const hiddenErrorCount =
|
|
6584
|
+
const hiddenErrorCount = useMemo12(() => {
|
|
4250
6585
|
if (!groupDefs?.length || fieldErrors.length === 0) return 0;
|
|
4251
6586
|
const hidden = hiddenGroupFieldNames(groupDefs, rootData);
|
|
4252
6587
|
if (hidden.size === 0) return 0;
|
|
4253
6588
|
return fieldErrors.filter((e) => hidden.has(pointerRoot(e.pointer))).length;
|
|
4254
6589
|
}, [groupDefs, fieldErrors, rootData]);
|
|
4255
|
-
|
|
6590
|
+
const formLevelErrors = useMemo12(() => {
|
|
6591
|
+
if (validationDisplay !== "always" && !submitAttempted) return [];
|
|
6592
|
+
return fieldErrors.filter((e) => e.pointer === "").map((e) => e.message);
|
|
6593
|
+
}, [fieldErrors, validationDisplay, submitAttempted]);
|
|
6594
|
+
return /* @__PURE__ */ jsx48(NestedFormContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx48(
|
|
4256
6595
|
FormContainer,
|
|
4257
6596
|
{
|
|
4258
6597
|
displayContents,
|
|
@@ -4265,21 +6604,23 @@ function JsonSchemaFormRenderer(props) {
|
|
|
4265
6604
|
disabled,
|
|
4266
6605
|
hasChanges: hasChanges || allowPristineSave,
|
|
4267
6606
|
isValid,
|
|
6607
|
+
isValidating,
|
|
4268
6608
|
saveWhenInvalid,
|
|
4269
6609
|
saveLabel,
|
|
4270
6610
|
cancelLabel,
|
|
4271
6611
|
showCancel,
|
|
4272
6612
|
leadingActions,
|
|
4273
6613
|
hiddenErrorCount,
|
|
6614
|
+
formLevelErrors,
|
|
4274
6615
|
children: layoutFields
|
|
4275
6616
|
}
|
|
4276
6617
|
) });
|
|
4277
6618
|
}
|
|
4278
6619
|
|
|
4279
6620
|
// src/hooks/useResolvedSchema.ts
|
|
4280
|
-
import { useMemo as
|
|
6621
|
+
import { useMemo as useMemo13 } from "react";
|
|
4281
6622
|
import {
|
|
4282
|
-
getSchemaProperties as
|
|
6623
|
+
getSchemaProperties as getSchemaProperties5,
|
|
4283
6624
|
resolveSchema as resolveSchema2
|
|
4284
6625
|
} from "@petrarca/sonnet-core/schema";
|
|
4285
6626
|
function useResolvedSchema({
|
|
@@ -4287,12 +6628,12 @@ function useResolvedSchema({
|
|
|
4287
6628
|
formData,
|
|
4288
6629
|
uiSchema
|
|
4289
6630
|
}) {
|
|
4290
|
-
const resolvedSchema =
|
|
6631
|
+
const resolvedSchema = useMemo13(
|
|
4291
6632
|
() => resolveSchema2(schema, formData),
|
|
4292
6633
|
[schema, formData]
|
|
4293
6634
|
);
|
|
4294
|
-
const orderedProperties =
|
|
4295
|
-
const properties =
|
|
6635
|
+
const orderedProperties = useMemo13(() => {
|
|
6636
|
+
const properties = getSchemaProperties5(resolvedSchema);
|
|
4296
6637
|
const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
|
|
4297
6638
|
const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
|
|
4298
6639
|
return orderProperties(properties, orderArray);
|
|
@@ -4300,8 +6641,13 @@ function useResolvedSchema({
|
|
|
4300
6641
|
return { resolvedSchema, orderedProperties };
|
|
4301
6642
|
}
|
|
4302
6643
|
export {
|
|
6644
|
+
AbsentReasonsWidget,
|
|
4303
6645
|
ArrayWidget,
|
|
6646
|
+
CheckboxGroupWidget,
|
|
4304
6647
|
CheckboxWidget,
|
|
6648
|
+
ChoiceWithTextWidget,
|
|
6649
|
+
CodeableConceptWidget,
|
|
6650
|
+
CodedValueWidget,
|
|
4305
6651
|
DEFAULT_TEMPLATES,
|
|
4306
6652
|
DEFAULT_WIDGETS,
|
|
4307
6653
|
DEFAULT_WIDGET_MAP,
|
|
@@ -4315,6 +6661,7 @@ export {
|
|
|
4315
6661
|
FormFieldRenderer,
|
|
4316
6662
|
FormFieldWrapper,
|
|
4317
6663
|
FormInput,
|
|
6664
|
+
FormKeyValueInput,
|
|
4318
6665
|
FormMultiSelect,
|
|
4319
6666
|
FormNumberInput,
|
|
4320
6667
|
FormQuantityInput,
|
|
@@ -4324,10 +6671,12 @@ export {
|
|
|
4324
6671
|
GroupErrorBadge,
|
|
4325
6672
|
JsonEditorWidget,
|
|
4326
6673
|
JsonSchemaFormRenderer,
|
|
6674
|
+
KeyValueWidget,
|
|
4327
6675
|
NumberWidget,
|
|
4328
6676
|
ObjectWidget,
|
|
4329
6677
|
OneOfWidget,
|
|
4330
6678
|
QuantityWidget,
|
|
6679
|
+
RadioWidget,
|
|
4331
6680
|
SectionsGroupTemplate,
|
|
4332
6681
|
SelectWidget,
|
|
4333
6682
|
StepperGroupTemplate,
|