@petrarca/sonnet-forms 0.8.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/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..." : emptyMessage }),
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..." : emptyMessage }),
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({ tag, index, isDisabled, onRemove }) {
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
- tag,
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__ */ jsx8(
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__ */ jsxs7(
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__ */ jsx8(
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__ */ jsx8(
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 React8, { useCallback as useCallback2, useEffect as useEffect2, useId as useId8, useMemo, useState as useState4 } from "react";
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 cn9 } from "@petrarca/sonnet-core";
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 jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
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 UnitAddon({
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__ */ jsx9(InputGroupAddon, { align: addonAlign, children: /* @__PURE__ */ jsx9("span", { className: "text-sm text-muted-foreground", children: units[0].label }) });
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__ */ jsx9(InputGroupAddon, { align: addonAlign, children: /* @__PURE__ */ jsxs8("div", { className: "relative flex items-center", children: [
1244
- /* @__PURE__ */ jsx9(
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__ */ jsx9("option", { value: u.value, children: u.label }, u.value))
1704
+ children: units.map((u) => /* @__PURE__ */ jsx11("option", { value: u.value, children: u.label }, u.value))
1253
1705
  }
1254
1706
  ),
1255
- /* @__PURE__ */ jsx9(ChevronsUpDown3, { className: "pointer-events-none absolute right-0 h-3.5 w-3.5 text-muted-foreground/60" })
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] = useState4(
1728
+ const [rawInput, setRawInput] = useState5(
1276
1729
  value?.value !== void 0 ? String(value.value) : ""
1277
1730
  );
1278
- const [validationError, setValidationError] = useState4("");
1279
- useEffect2(() => {
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 = React8.forwardRef((allProps, ref) => {
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 = useId8();
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__ */ jsx9(
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
- [units, currentUnit, addonAlign, disabled, readOnly, handleUnitChange]
1876
+ [
1877
+ units,
1878
+ freeUnit,
1879
+ currentUnit,
1880
+ addonAlign,
1881
+ disabled,
1882
+ readOnly,
1883
+ handleUnitChange
1884
+ ]
1422
1885
  );
1423
- return /* @__PURE__ */ jsx9(
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__ */ jsxs8(
1896
+ children: /* @__PURE__ */ jsxs10(
1434
1897
  InputGroup,
1435
1898
  {
1436
1899
  "data-disabled": disabled || void 0,
1437
- className: cn9(resolvedError && "border-destructive"),
1900
+ className: cn10(resolvedError && "border-destructive"),
1438
1901
  children: [
1439
- /* @__PURE__ */ jsx9(
1902
+ /* @__PURE__ */ jsx11(
1440
1903
  InputGroupInput,
1441
1904
  {
1442
1905
  id: inputId,
@@ -1474,9 +1937,9 @@ var FormQuantityInput = React8.forwardRef((allProps, ref) => {
1474
1937
  FormQuantityInput.displayName = "FormQuantityInput";
1475
1938
 
1476
1939
  // src/FormActions.tsx
1477
- import { Button as Button3 } from "@petrarca/sonnet-ui";
1940
+ import { Button as Button4 } from "@petrarca/sonnet-ui";
1478
1941
  import { SimpleGroup } from "@petrarca/sonnet-ui";
1479
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1942
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1480
1943
  function isSaveDisabled(args) {
1481
1944
  if (args.disabled || !args.hasChanges || args.isValidating) return true;
1482
1945
  return args.saveWhenInvalid === "disable" && !args.isValid;
@@ -1502,11 +1965,11 @@ function FormActions({
1502
1965
  saveWhenInvalid
1503
1966
  });
1504
1967
  const keepFocus = (e) => e.preventDefault();
1505
- return /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between mt-4", children: [
1506
- /* @__PURE__ */ jsx10("div", { className: "flex items-center gap-2", children: leadingActions }),
1507
- /* @__PURE__ */ jsxs9(SimpleGroup, { justify: "flex-end", children: [
1508
- showCancel && /* @__PURE__ */ jsx10(
1509
- Button3,
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,
1510
1973
  {
1511
1974
  variant: "outline",
1512
1975
  onClick: onCancel,
@@ -1515,8 +1978,8 @@ function FormActions({
1515
1978
  children: cancelLabel
1516
1979
  }
1517
1980
  ),
1518
- /* @__PURE__ */ jsx10(
1519
- Button3,
1981
+ /* @__PURE__ */ jsx12(
1982
+ Button4,
1520
1983
  {
1521
1984
  onClick: onSave,
1522
1985
  onMouseDown: keepFocus,
@@ -1534,7 +1997,7 @@ import { ChevronDown, ChevronRight } from "lucide-react";
1534
1997
  import { Card } from "@petrarca/sonnet-ui";
1535
1998
  import { useDisclosure } from "@petrarca/sonnet-core/hooks";
1536
1999
  import { Collapsible, CollapsibleContent } from "@petrarca/sonnet-ui";
1537
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2000
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1538
2001
  function ExtraPropertiesCard({ properties, schema }) {
1539
2002
  const [opened, { toggle }] = useDisclosure(false);
1540
2003
  const extraProperties = useMemo2(() => {
@@ -1546,32 +2009,35 @@ function ExtraPropertiesCard({ properties, schema }) {
1546
2009
  return Object.keys(extra).length > 0 ? extra : null;
1547
2010
  }, [properties, schema]);
1548
2011
  if (!extraProperties) return null;
1549
- return /* @__PURE__ */ jsxs10(Card, { className: "border p-3", children: [
1550
- /* @__PURE__ */ jsxs10(
2012
+ return /* @__PURE__ */ jsxs12(Card, { className: "border p-3", children: [
2013
+ /* @__PURE__ */ jsxs12(
1551
2014
  "p",
1552
2015
  {
1553
2016
  className: `flex items-center gap-1 text-sm font-medium cursor-pointer select-none ${opened ? "mb-1" : ""}`,
1554
2017
  onClick: toggle,
1555
2018
  children: [
1556
- opened ? /* @__PURE__ */ jsx11(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx11(ChevronRight, { className: "h-3.5 w-3.5" }),
2019
+ opened ? /* @__PURE__ */ jsx13(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx13(ChevronRight, { className: "h-3.5 w-3.5" }),
1557
2020
  "Extra Properties (not in schema)"
1558
2021
  ]
1559
2022
  }
1560
2023
  ),
1561
- /* @__PURE__ */ jsx11(Collapsible, { open: opened, children: /* @__PURE__ */ jsx11(CollapsibleContent, { children: /* @__PURE__ */ jsx11("pre", { className: "text-xs bg-muted p-2 rounded overflow-x-auto", children: /* @__PURE__ */ jsx11("code", { children: JSON.stringify(extraProperties, null, 2) }) }) }) })
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) }) }) }) })
1562
2025
  ] });
1563
2026
  }
1564
2027
  var ExtraPropertiesCard_default = ExtraPropertiesCard;
1565
2028
 
1566
2029
  // src/JsonSchemaFormRenderer.tsx
1567
- import React13, { useCallback as useCallback8, useContext as useContext2, useMemo as useMemo9 } from "react";
2030
+ import React18, { useCallback as useCallback9, useContext as useContext2, useMemo as useMemo12 } from "react";
1568
2031
  import { SimpleStack } from "@petrarca/sonnet-ui";
1569
2032
  import { AlertCircle as AlertCircle2 } from "lucide-react";
1570
2033
  import {
1571
2034
  WIDTH_TOKEN_REMS
1572
2035
  } from "@petrarca/sonnet-core/schema";
1573
- import { isFieldRequired, evaluateUiRule } from "@petrarca/sonnet-core/schema";
1574
- import { FORM_ITEM_ID_FIELD as FORM_ITEM_ID_FIELD2 } from "@petrarca/sonnet-core";
2036
+ import {
2037
+ isFieldRequired,
2038
+ evaluateUiRule,
2039
+ stripFormItemIds as stripFormItemIds2
2040
+ } from "@petrarca/sonnet-core/schema";
1575
2041
 
1576
2042
  // src/widgets/types.ts
1577
2043
  function firstError(rawErrors) {
@@ -1579,7 +2045,7 @@ function firstError(rawErrors) {
1579
2045
  }
1580
2046
 
1581
2047
  // src/widgets/TextInputWidget.tsx
1582
- import { jsx as jsx12 } from "react/jsx-runtime";
2048
+ import { jsx as jsx14 } from "react/jsx-runtime";
1583
2049
  function inputTypeFromFormat(format) {
1584
2050
  if (format === "email") return "email";
1585
2051
  if (format === "uri" || format === "url") return "url";
@@ -1604,7 +2070,7 @@ function TextInputWidget({
1604
2070
  }) {
1605
2071
  const error = firstError(rawErrors);
1606
2072
  const compact = options?.compact;
1607
- return /* @__PURE__ */ jsx12(
2073
+ return /* @__PURE__ */ jsx14(
1608
2074
  FormInput,
1609
2075
  {
1610
2076
  id,
@@ -1631,7 +2097,7 @@ function TextInputWidget({
1631
2097
  }
1632
2098
 
1633
2099
  // src/widgets/TextareaWidget.tsx
1634
- import { jsx as jsx13 } from "react/jsx-runtime";
2100
+ import { jsx as jsx15 } from "react/jsx-runtime";
1635
2101
  function TextareaWidget({
1636
2102
  id,
1637
2103
  name,
@@ -1652,7 +2118,7 @@ function TextareaWidget({
1652
2118
  const minRows = options.rows || options.minRows || 3;
1653
2119
  const autosize = options.autosize !== false;
1654
2120
  const error = firstError(rawErrors);
1655
- return /* @__PURE__ */ jsx13(
2121
+ return /* @__PURE__ */ jsx15(
1656
2122
  FormTextarea,
1657
2123
  {
1658
2124
  id,
@@ -1678,7 +2144,7 @@ function TextareaWidget({
1678
2144
  }
1679
2145
 
1680
2146
  // src/widgets/NumberWidget.tsx
1681
- import { jsx as jsx14 } from "react/jsx-runtime";
2147
+ import { jsx as jsx16 } from "react/jsx-runtime";
1682
2148
  function NumberWidget({
1683
2149
  id,
1684
2150
  name,
@@ -1700,7 +2166,7 @@ function NumberWidget({
1700
2166
  const step = options.step ?? (isInteger ? 1 : void 0);
1701
2167
  const allowDecimal = options.allowDecimal ?? !isInteger;
1702
2168
  const error = firstError(rawErrors);
1703
- return /* @__PURE__ */ jsx14(
2169
+ return /* @__PURE__ */ jsx16(
1704
2170
  FormNumberInput,
1705
2171
  {
1706
2172
  id,
@@ -1728,7 +2194,7 @@ function NumberWidget({
1728
2194
  // src/widgets/CheckboxWidget.tsx
1729
2195
  import { useCallback as useCallback3 } from "react";
1730
2196
  import { isNullableBoolean } from "@petrarca/sonnet-core/schema";
1731
- import { jsx as jsx15 } from "react/jsx-runtime";
2197
+ import { jsx as jsx17 } from "react/jsx-runtime";
1732
2198
  function CheckboxWidget({
1733
2199
  id,
1734
2200
  value,
@@ -1747,12 +2213,12 @@ function CheckboxWidget({
1747
2213
  const compact = options?.compact;
1748
2214
  const isTriState = isNullableBoolean(propertySchema);
1749
2215
  const isChecked = value === true;
1750
- const isIndeterminate = isTriState && value === null;
2216
+ const isIndeterminate = isTriState && value == null;
1751
2217
  const checkedState = isIndeterminate ? "indeterminate" : isChecked;
1752
2218
  const handleChange = useCallback3(
1753
2219
  (_newChecked) => {
1754
2220
  if (isTriState) {
1755
- if (value === null) {
2221
+ if (value == null) {
1756
2222
  onChange(true);
1757
2223
  } else if (value === true) {
1758
2224
  onChange(false);
@@ -1765,7 +2231,7 @@ function CheckboxWidget({
1765
2231
  },
1766
2232
  [isTriState, value, onChange]
1767
2233
  );
1768
- return /* @__PURE__ */ jsx15(
2234
+ return /* @__PURE__ */ jsx17(
1769
2235
  FormCheckbox,
1770
2236
  {
1771
2237
  id,
@@ -1785,15 +2251,7 @@ function CheckboxWidget({
1785
2251
 
1786
2252
  // src/widgets/SelectWidget.tsx
1787
2253
  import { useRef as useRef3 } from "react";
1788
- import { extractEnumValues } from "@petrarca/sonnet-core/schema";
1789
- import { jsx as jsx16 } from "react/jsx-runtime";
1790
- function buildSelectOptions(propertySchema, enumNames) {
1791
- const enumValues = extractEnumValues(propertySchema);
1792
- return enumValues.map((val) => {
1793
- const key = String(val);
1794
- return { value: key, label: enumNames[key] || key };
1795
- });
1796
- }
2254
+ import { jsx as jsx18 } from "react/jsx-runtime";
1797
2255
  function normalizeMultiValue(value) {
1798
2256
  if (Array.isArray(value)) return value.map(String);
1799
2257
  if (value) return [String(value)];
@@ -1833,7 +2291,7 @@ function SelectWidget(props) {
1833
2291
  const triggerClassName = compact ? "h-8 text-xs" : void 0;
1834
2292
  const handleBlur = () => onBlur?.(id, valueRef.current);
1835
2293
  if (isMultiple) {
1836
- return /* @__PURE__ */ jsx16(
2294
+ return /* @__PURE__ */ jsx18(
1837
2295
  FormMultiSelect,
1838
2296
  {
1839
2297
  id,
@@ -1857,7 +2315,7 @@ function SelectWidget(props) {
1857
2315
  }
1858
2316
  );
1859
2317
  }
1860
- return /* @__PURE__ */ jsx16(
2318
+ return /* @__PURE__ */ jsx18(
1861
2319
  FormSelect,
1862
2320
  {
1863
2321
  id,
@@ -1885,7 +2343,7 @@ function SelectWidget(props) {
1885
2343
  }
1886
2344
 
1887
2345
  // src/widgets/TagsInputWidget.tsx
1888
- import { jsx as jsx17 } from "react/jsx-runtime";
2346
+ import { jsx as jsx19 } from "react/jsx-runtime";
1889
2347
  function TagsInputWidget({
1890
2348
  id,
1891
2349
  name,
@@ -1902,7 +2360,7 @@ function TagsInputWidget({
1902
2360
  }) {
1903
2361
  const error = firstError(rawErrors);
1904
2362
  const tags = Array.isArray(value) ? value.map(String) : [];
1905
- return /* @__PURE__ */ jsx17(
2363
+ return /* @__PURE__ */ jsx19(
1906
2364
  FormTagsInput,
1907
2365
  {
1908
2366
  id,
@@ -1925,9 +2383,13 @@ function TagsInputWidget({
1925
2383
  }
1926
2384
 
1927
2385
  // src/widgets/JsonEditorWidget.tsx
1928
- import { useCallback as useCallback4, useState as useState5 } from "react";
1929
- import { JsonEditor } from "@petrarca/sonnet-ui/json-editor";
1930
- import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
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
+ );
1931
2393
  var FULL_DOCUMENT = ["/"];
1932
2394
  function JsonEditorWidget({
1933
2395
  id,
@@ -1938,7 +2400,7 @@ function JsonEditorWidget({
1938
2400
  required,
1939
2401
  options
1940
2402
  }) {
1941
- const [raw, setRaw] = useState5(
2403
+ const [raw, setRaw] = useState6(
1942
2404
  value != null ? JSON.stringify(value, null, 2) : ""
1943
2405
  );
1944
2406
  const height = options?.height ?? "240px";
@@ -1953,54 +2415,116 @@ function JsonEditorWidget({
1953
2415
  },
1954
2416
  [onChange]
1955
2417
  );
1956
- return /* @__PURE__ */ jsxs11("div", { className: "space-y-2", children: [
1957
- label && /* @__PURE__ */ jsxs11("label", { htmlFor: id, className: "text-sm font-medium leading-none", children: [
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: [
1958
2420
  label,
1959
- required && /* @__PURE__ */ jsx18("span", { className: "text-destructive ml-1", children: "*" })
2421
+ required && /* @__PURE__ */ jsx20("span", { className: "text-destructive ml-1", children: "*" })
1960
2422
  ] }),
1961
- description && /* @__PURE__ */ jsx18("p", { className: "text-sm text-muted-foreground", children: description }),
1962
- /* @__PURE__ */ jsx18(
1963
- JsonEditor,
2423
+ description && /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: description }),
2424
+ /* @__PURE__ */ jsx20(
2425
+ Suspense,
1964
2426
  {
1965
- value: raw,
1966
- editablePaths: FULL_DOCUMENT,
1967
- onChange: handleChange,
1968
- height,
1969
- className: "rounded-md border overflow-hidden"
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
+ )
1970
2445
  }
1971
2446
  )
1972
2447
  ] });
1973
2448
  }
1974
2449
 
1975
- // src/widgets/EntitySelectWidget.tsx
1976
- import { useMemo as useMemo3 } from "react";
1977
- import { EntitySelect } from "@petrarca/sonnet-ui";
1978
- import { useFetcherFactory } from "@petrarca/sonnet-core/entityOptions";
1979
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
1980
- var VALID_MODES = ["eager", "typeahead", "explicit"];
1981
- function resolveSelectMode(options) {
1982
- const rawMode = options?.mode;
1983
- if (typeof rawMode === "string" && VALID_MODES.includes(rawMode)) {
1984
- return rawMode;
1985
- }
1986
- return "eager";
1987
- }
1988
- function resolveMinChars(options) {
1989
- const rawMinChars = options?.minChars;
1990
- if (typeof rawMinChars === "number" && rawMinChars >= 1) return rawMinChars;
1991
- return 1;
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";
1992
2454
  }
1993
- function buildRestConfig(options) {
1994
- if (!("endpoint" in options) || !options.endpoint) return null;
1995
- const rawExtra = options.extraParams;
1996
- const extraParams = rawExtra && typeof rawExtra === "object" ? rawExtra : void 0;
1997
- return {
1998
- endpoint: String(options.endpoint),
1999
- valueKey: String(options.valueKey ?? "id"),
2000
- labelKey: String(options.labelKey ?? "name"),
2001
- searchKey: options.searchKey ? String(options.searchKey) : void 0,
2002
- extraParams
2003
- };
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;
2516
+ }
2517
+ function buildRestConfig(options) {
2518
+ if (!("endpoint" in options) || !options.endpoint) return null;
2519
+ const rawExtra = options.extraParams;
2520
+ const extraParams = rawExtra && typeof rawExtra === "object" ? rawExtra : void 0;
2521
+ return {
2522
+ endpoint: String(options.endpoint),
2523
+ valueKey: String(options.valueKey ?? "id"),
2524
+ labelKey: String(options.labelKey ?? "name"),
2525
+ searchKey: options.searchKey ? String(options.searchKey) : void 0,
2526
+ extraParams
2527
+ };
2004
2528
  }
2005
2529
  function buildGraphqlConfig(options) {
2006
2530
  if (!("query" in options) || !options.query) return null;
@@ -2048,13 +2572,13 @@ function EntitySelectWidget({
2048
2572
  [fetcherConfig, name]
2049
2573
  );
2050
2574
  if (!fetcher) {
2051
- return /* @__PURE__ */ jsxs12("div", { className: "text-sm text-destructive", children: [
2575
+ return /* @__PURE__ */ jsxs14("div", { className: "text-sm text-destructive", children: [
2052
2576
  'EntitySelectWidget: missing endpoint or query in x-ui-options for field "',
2053
2577
  name,
2054
2578
  '"'
2055
2579
  ] });
2056
2580
  }
2057
- return /* @__PURE__ */ jsx19(
2581
+ return /* @__PURE__ */ jsx22(
2058
2582
  EntitySelect,
2059
2583
  {
2060
2584
  id,
@@ -2100,7 +2624,7 @@ function useNestedFormContext() {
2100
2624
  }
2101
2625
 
2102
2626
  // src/templates/DefaultObjectContainerTemplate.tsx
2103
- import { useState as useState6 } from "react";
2627
+ import { useState as useState7 } from "react";
2104
2628
  import { ChevronDown as ChevronDown2, ChevronRight as ChevronRight2 } from "lucide-react";
2105
2629
  import { Card as Card2, CardContent, CardHeader, CardTitle } from "@petrarca/sonnet-ui";
2106
2630
  import {
@@ -2108,18 +2632,18 @@ import {
2108
2632
  CollapsibleContent as CollapsibleContent2,
2109
2633
  CollapsibleTrigger
2110
2634
  } from "@petrarca/sonnet-ui";
2111
- import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
2635
+ import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
2112
2636
  function CardVariant({
2113
2637
  children,
2114
2638
  label,
2115
2639
  description
2116
2640
  }) {
2117
- return /* @__PURE__ */ jsxs13(Card2, { children: [
2118
- label && /* @__PURE__ */ jsxs13(CardHeader, { className: "pb-3", children: [
2119
- /* @__PURE__ */ jsx20(CardTitle, { className: "text-base", children: label }),
2120
- description && /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: description })
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 })
2121
2645
  ] }),
2122
- /* @__PURE__ */ jsx20(CardContent, { children })
2646
+ /* @__PURE__ */ jsx23(CardContent, { children })
2123
2647
  ] });
2124
2648
  }
2125
2649
  function SectionVariant({
@@ -2127,12 +2651,12 @@ function SectionVariant({
2127
2651
  label,
2128
2652
  description
2129
2653
  }) {
2130
- return /* @__PURE__ */ jsxs13("div", { className: "space-y-3", children: [
2131
- label && /* @__PURE__ */ jsxs13("div", { children: [
2132
- /* @__PURE__ */ jsx20("h4", { className: "text-sm font-medium", children: label }),
2133
- description && /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: description })
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 })
2134
2658
  ] }),
2135
- /* @__PURE__ */ jsx20("div", { className: "pl-3 border-l-2 border-muted", children })
2659
+ /* @__PURE__ */ jsx23("div", { className: "pl-3 border-l-2 border-muted", children })
2136
2660
  ] });
2137
2661
  }
2138
2662
  function DefaultObjectContainerTemplate({
@@ -2144,10 +2668,10 @@ function DefaultObjectContainerTemplate({
2144
2668
  }) {
2145
2669
  switch (variant) {
2146
2670
  case "card":
2147
- return /* @__PURE__ */ jsx20(CardVariant, { label, description, children });
2671
+ return /* @__PURE__ */ jsx23(CardVariant, { label, description, children });
2148
2672
  case "collapsible":
2149
- if (!label) return /* @__PURE__ */ jsx20(SectionVariant, { children });
2150
- return /* @__PURE__ */ jsx20(
2673
+ if (!label) return /* @__PURE__ */ jsx23(SectionVariant, { children });
2674
+ return /* @__PURE__ */ jsx23(
2151
2675
  CollapsibleVariant,
2152
2676
  {
2153
2677
  label,
@@ -2157,12 +2681,12 @@ function DefaultObjectContainerTemplate({
2157
2681
  }
2158
2682
  );
2159
2683
  case "inline":
2160
- return /* @__PURE__ */ jsx20("div", { children });
2684
+ return /* @__PURE__ */ jsx23("div", { children });
2161
2685
  case "borderless":
2162
- return /* @__PURE__ */ jsx20("div", { className: "space-y-4", children });
2686
+ return /* @__PURE__ */ jsx23("div", { className: "space-y-4", children });
2163
2687
  case "section":
2164
2688
  default:
2165
- return /* @__PURE__ */ jsx20(SectionVariant, { label, description, children });
2689
+ return /* @__PURE__ */ jsx23(SectionVariant, { label, description, children });
2166
2690
  }
2167
2691
  }
2168
2692
  function CollapsibleVariant({
@@ -2171,26 +2695,26 @@ function CollapsibleVariant({
2171
2695
  defaultOpen,
2172
2696
  children
2173
2697
  }) {
2174
- const [open, setOpen] = useState6(defaultOpen);
2175
- return /* @__PURE__ */ jsxs13(Collapsible2, { open, onOpenChange: setOpen, children: [
2176
- /* @__PURE__ */ jsx20(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs13(
2698
+ const [open, setOpen] = useState7(defaultOpen);
2699
+ return /* @__PURE__ */ jsxs15(Collapsible2, { open, onOpenChange: setOpen, children: [
2700
+ /* @__PURE__ */ jsx23(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs15(
2177
2701
  "button",
2178
2702
  {
2179
2703
  type: "button",
2180
2704
  className: "flex w-full items-center gap-2 text-sm font-medium hover:text-foreground/80",
2181
2705
  children: [
2182
- open ? /* @__PURE__ */ jsx20(ChevronDown2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx20(ChevronRight2, { className: "h-4 w-4" }),
2706
+ open ? /* @__PURE__ */ jsx23(ChevronDown2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx23(ChevronRight2, { className: "h-4 w-4" }),
2183
2707
  label
2184
2708
  ]
2185
2709
  }
2186
2710
  ) }),
2187
- description && /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground ml-6", children: description }),
2188
- /* @__PURE__ */ jsx20(CollapsibleContent2, { className: "mt-2 pl-3 border-l-2 border-muted", children })
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 })
2189
2713
  ] });
2190
2714
  }
2191
2715
 
2192
2716
  // src/widgets/ObjectWidget.tsx
2193
- import { Fragment as Fragment3, jsx as jsx21 } from "react/jsx-runtime";
2717
+ import { Fragment as Fragment3, jsx as jsx24 } from "react/jsx-runtime";
2194
2718
  function buildSubSchema(prop) {
2195
2719
  const properties = prop.properties;
2196
2720
  if (!properties) return null;
@@ -2256,7 +2780,7 @@ function ObjectWidget({
2256
2780
  globalOptions,
2257
2781
  layout
2258
2782
  );
2259
- const content = /* @__PURE__ */ jsx21(
2783
+ const content = /* @__PURE__ */ jsx24(
2260
2784
  Renderer,
2261
2785
  {
2262
2786
  schema: subSchema,
@@ -2271,8 +2795,8 @@ function ObjectWidget({
2271
2795
  onBlur: onBlur ? () => onBlur(id, value) : void 0
2272
2796
  }
2273
2797
  );
2274
- if (displayContents) return /* @__PURE__ */ jsx21(Fragment3, { children: content });
2275
- return /* @__PURE__ */ jsx21(
2798
+ if (displayContents) return /* @__PURE__ */ jsx24(Fragment3, { children: content });
2799
+ return /* @__PURE__ */ jsx24(
2276
2800
  ContainerTemplate,
2277
2801
  {
2278
2802
  label,
@@ -2286,15 +2810,15 @@ function ObjectWidget({
2286
2810
 
2287
2811
  // src/widgets/ArrayWidget.tsx
2288
2812
  import { useCallback as useCallback5, useMemo as useMemo5 } from "react";
2289
- import { Plus as Plus2 } from "lucide-react";
2290
- import { Button as Button5 } from "@petrarca/sonnet-ui";
2813
+ import { Plus as Plus3 } from "lucide-react";
2814
+ import { Button as Button6 } from "@petrarca/sonnet-ui";
2291
2815
  import { applySchemaDefaults } from "@petrarca/sonnet-core/schema";
2292
2816
  import { FORM_ITEM_ID_FIELD, generateId } from "@petrarca/sonnet-core";
2293
2817
 
2294
2818
  // src/templates/DefaultArrayItemTemplate.tsx
2295
2819
  import { Trash2, ChevronUp, ChevronDown as ChevronDown3 } from "lucide-react";
2296
- import { Button as Button4 } from "@petrarca/sonnet-ui";
2297
- import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
2820
+ import { Button as Button5 } from "@petrarca/sonnet-ui";
2821
+ import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
2298
2822
  function DefaultArrayItemTemplate({
2299
2823
  children,
2300
2824
  index,
@@ -2308,16 +2832,16 @@ function DefaultArrayItemTemplate({
2308
2832
  onMoveDown
2309
2833
  }) {
2310
2834
  const showControls = canRemove || orderable;
2311
- return /* @__PURE__ */ jsxs14(
2835
+ return /* @__PURE__ */ jsxs16(
2312
2836
  "div",
2313
2837
  {
2314
2838
  "data-fid": fid,
2315
2839
  className: "relative flex gap-2 rounded-md border border-border/50 bg-muted/20 p-3",
2316
2840
  children: [
2317
- /* @__PURE__ */ jsx22("div", { className: "flex-1 min-w-0", children }),
2318
- showControls && /* @__PURE__ */ jsxs14("div", { className: "flex flex-col gap-1 pt-0.5", children: [
2319
- orderable && index > 0 && /* @__PURE__ */ jsx22(
2320
- Button4,
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,
2321
2845
  {
2322
2846
  type: "button",
2323
2847
  variant: "ghost",
@@ -2325,11 +2849,11 @@ function DefaultArrayItemTemplate({
2325
2849
  disabled,
2326
2850
  onClick: onMoveUp,
2327
2851
  title: "Move up",
2328
- children: /* @__PURE__ */ jsx22(ChevronUp, { className: "h-3.5 w-3.5" })
2852
+ children: /* @__PURE__ */ jsx25(ChevronUp, { className: "h-3.5 w-3.5" })
2329
2853
  }
2330
2854
  ),
2331
- orderable && index < total - 1 && /* @__PURE__ */ jsx22(
2332
- Button4,
2855
+ orderable && index < total - 1 && /* @__PURE__ */ jsx25(
2856
+ Button5,
2333
2857
  {
2334
2858
  type: "button",
2335
2859
  variant: "ghost",
@@ -2337,11 +2861,11 @@ function DefaultArrayItemTemplate({
2337
2861
  disabled,
2338
2862
  onClick: onMoveDown,
2339
2863
  title: "Move down",
2340
- children: /* @__PURE__ */ jsx22(ChevronDown3, { className: "h-3.5 w-3.5" })
2864
+ children: /* @__PURE__ */ jsx25(ChevronDown3, { className: "h-3.5 w-3.5" })
2341
2865
  }
2342
2866
  ),
2343
- canRemove && /* @__PURE__ */ jsx22(
2344
- Button4,
2867
+ canRemove && /* @__PURE__ */ jsx25(
2868
+ Button5,
2345
2869
  {
2346
2870
  type: "button",
2347
2871
  variant: "ghost",
@@ -2350,7 +2874,7 @@ function DefaultArrayItemTemplate({
2350
2874
  onClick: onRemove,
2351
2875
  title: "Remove item",
2352
2876
  className: "text-muted-foreground hover:text-destructive",
2353
- children: /* @__PURE__ */ jsx22(Trash2, { className: "h-3.5 w-3.5" })
2877
+ children: /* @__PURE__ */ jsx25(Trash2, { className: "h-3.5 w-3.5" })
2354
2878
  }
2355
2879
  )
2356
2880
  ] })
@@ -2367,7 +2891,7 @@ function buildGridTemplate(layout, canRemove, fieldCount, orderable = false) {
2367
2891
  }
2368
2892
 
2369
2893
  // src/templates/DefaultArrayHeaderTemplate.tsx
2370
- import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
2894
+ import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
2371
2895
  function DefaultArrayHeaderTemplate({
2372
2896
  itemSchema,
2373
2897
  layout,
@@ -2381,7 +2905,7 @@ function DefaultArrayHeaderTemplate({
2381
2905
  const properties = itemSchema.properties ?? {};
2382
2906
  const order = itemSchema["x-ui-order"];
2383
2907
  const fieldNames = order ? order.filter((k) => k in properties) : Object.keys(properties);
2384
- return /* @__PURE__ */ jsxs15(
2908
+ return /* @__PURE__ */ jsxs17(
2385
2909
  "div",
2386
2910
  {
2387
2911
  style: {
@@ -2390,16 +2914,16 @@ function DefaultArrayHeaderTemplate({
2390
2914
  gap: `${gap}px`
2391
2915
  },
2392
2916
  children: [
2393
- fieldNames.map((name) => /* @__PURE__ */ jsx23("span", { className: "text-xs font-medium text-muted-foreground", children: properties[name]?.title ?? name }, name)),
2394
- orderable && /* @__PURE__ */ jsx23("span", {}),
2395
- canRemove && /* @__PURE__ */ jsx23("span", {})
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", {})
2396
2920
  ]
2397
2921
  }
2398
2922
  );
2399
2923
  }
2400
2924
 
2401
2925
  // src/widgets/ArrayWidget.tsx
2402
- import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
2926
+ import { jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
2403
2927
  function countFields(itemSchema) {
2404
2928
  return Object.keys(itemSchema.properties ?? {}).length;
2405
2929
  }
@@ -2438,9 +2962,9 @@ function buildSubGlobalOptions(layout, isHorizontal) {
2438
2962
  }
2439
2963
  function ArrayHeader({ label, description }) {
2440
2964
  if (!label) return null;
2441
- return /* @__PURE__ */ jsxs16("div", { children: [
2442
- /* @__PURE__ */ jsx24("h4", { className: "text-sm font-medium", children: label }),
2443
- description && /* @__PURE__ */ jsx24("p", { className: "text-xs text-muted-foreground", children: description })
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 })
2444
2968
  ] });
2445
2969
  }
2446
2970
  function useArrayHandlers(items, onChange, propertySchema) {
@@ -2488,19 +3012,20 @@ function ArrayWidget({
2488
3012
  description,
2489
3013
  options,
2490
3014
  schema,
2491
- name
3015
+ name,
3016
+ onBlur
2492
3017
  }) {
2493
3018
  const { templates } = useNestedFormContext();
2494
3019
  const items = useMemo5(() => ensureItemIds(value), [value]);
2495
3020
  const { handleItemChange, handleAdd, handleRemove, handleMove } = useArrayHandlers(items, onChange, propertySchema);
2496
3021
  const itemSchema = propertySchema.items;
2497
3022
  if (!itemSchema?.properties) {
2498
- return /* @__PURE__ */ jsx24("p", { className: "text-sm text-destructive", children: 'ArrayWidget requires items with type "object" and properties.' });
3023
+ return /* @__PURE__ */ jsx27("p", { className: "text-sm text-destructive", children: 'ArrayWidget requires items with type "object" and properties.' });
2499
3024
  }
2500
3025
  const layout = resolveLayout2(uiSchema, propertySchema);
2501
3026
  const isHorizontal = layout.direction === "horizontal";
2502
3027
  const subGlobalOptions = buildSubGlobalOptions(layout, isHorizontal);
2503
- return /* @__PURE__ */ jsx24(
3028
+ return /* @__PURE__ */ jsx27(
2504
3029
  ArrayWidgetInner,
2505
3030
  {
2506
3031
  items,
@@ -2522,7 +3047,8 @@ function ArrayWidget({
2522
3047
  onItemChange: handleItemChange,
2523
3048
  onAdd: handleAdd,
2524
3049
  onRemove: handleRemove,
2525
- onMove: handleMove
3050
+ onMove: handleMove,
3051
+ onBlur
2526
3052
  }
2527
3053
  );
2528
3054
  }
@@ -2582,7 +3108,8 @@ function ArrayWidgetInner({
2582
3108
  onItemChange,
2583
3109
  onAdd,
2584
3110
  onRemove,
2585
- onMove
3111
+ onMove,
3112
+ onBlur
2586
3113
  }) {
2587
3114
  const constraints = resolveConstraints(
2588
3115
  propertySchema,
@@ -2599,10 +3126,10 @@ function ArrayWidgetInner({
2599
3126
  );
2600
3127
  const fieldCount = countFields(itemSchema);
2601
3128
  const itemOptions = buildItemOptions(isHorizontal, subGlobalOptions);
2602
- return /* @__PURE__ */ jsxs16("div", { className: isHorizontal ? "space-y-1" : "space-y-3", children: [
2603
- /* @__PURE__ */ jsx24(ArrayHeader, { label, description }),
2604
- items.length === 0 && display.emptyMessage && /* @__PURE__ */ jsx24("p", { className: "text-sm text-muted-foreground italic", children: display.emptyMessage }),
2605
- items.length > 0 && /* @__PURE__ */ jsx24(
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(
2606
3133
  HeaderTemplate,
2607
3134
  {
2608
3135
  itemSchema,
@@ -2612,7 +3139,7 @@ function ArrayWidgetInner({
2612
3139
  fieldCount
2613
3140
  }
2614
3141
  ),
2615
- items.map((item, index) => /* @__PURE__ */ jsx24(
3142
+ items.map((item, index) => /* @__PURE__ */ jsx27(
2616
3143
  ActiveItemTemplate,
2617
3144
  {
2618
3145
  index,
@@ -2626,7 +3153,7 @@ function ArrayWidgetInner({
2626
3153
  onRemove: () => onRemove(index),
2627
3154
  onMoveUp: index > 0 ? () => onMove(index, index - 1) : void 0,
2628
3155
  onMoveDown: index < items.length - 1 ? () => onMove(index, index + 1) : void 0,
2629
- children: /* @__PURE__ */ jsx24(
3156
+ children: /* @__PURE__ */ jsx27(
2630
3157
  ObjectWidget,
2631
3158
  {
2632
3159
  id: `field-${name}-${index}`,
@@ -2641,14 +3168,15 @@ function ArrayWidgetInner({
2641
3168
  description: void 0,
2642
3169
  disabled,
2643
3170
  readonly,
2644
- options: itemOptions
3171
+ options: itemOptions,
3172
+ onBlur
2645
3173
  }
2646
3174
  )
2647
3175
  },
2648
3176
  item[FORM_ITEM_ID_FIELD]
2649
3177
  )),
2650
- canAdd && /* @__PURE__ */ jsxs16(
2651
- Button5,
3178
+ canAdd && /* @__PURE__ */ jsxs18(
3179
+ Button6,
2652
3180
  {
2653
3181
  type: "button",
2654
3182
  variant: "outline",
@@ -2657,87 +3185,1221 @@ function ArrayWidgetInner({
2657
3185
  disabled,
2658
3186
  className: "gap-1.5",
2659
3187
  children: [
2660
- /* @__PURE__ */ jsx24(Plus2, { className: "h-3.5 w-3.5" }),
3188
+ /* @__PURE__ */ jsx27(Plus3, { className: "h-3.5 w-3.5" }),
2661
3189
  display.addLabel
2662
3190
  ]
2663
3191
  }
2664
3192
  )
2665
3193
  ] });
2666
3194
  }
2667
-
2668
- // src/widgets/QuantityWidget.tsx
2669
- import { useCallback as useCallback6, useRef as useRef4 } from "react";
2670
- import { jsx as jsx25 } from "react/jsx-runtime";
2671
- function isUnitOption(u) {
2672
- return typeof u === "object" && u !== null && typeof u.value === "string" && typeof u.label === "string";
3195
+
3196
+ // src/widgets/QuantityWidget.tsx
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";
3200
+ function isUnitOption(u) {
3201
+ return typeof u === "object" && u !== null && typeof u.value === "string" && typeof u.label === "string";
3202
+ }
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;
3216
+ const precision = typeof options.precision === "number" ? options.precision : void 0;
3217
+ const step = typeof options.step === "number" ? options.step : void 0;
3218
+ const unitPosition = options.unitPosition === "prefix" || options.unitPosition === "suffix" ? options.unitPosition : void 0;
3219
+ return { units, precision, step, unitPosition };
3220
+ }
3221
+ function QuantityWidget({
3222
+ id,
3223
+ name,
3224
+ value,
3225
+ onChange,
3226
+ onBlur,
3227
+ propertySchema,
3228
+ required,
3229
+ disabled,
3230
+ readonly,
3231
+ rawErrors,
3232
+ label,
3233
+ description,
3234
+ placeholder,
3235
+ autofocus,
3236
+ options = {}
3237
+ }) {
3238
+ const error = firstError(rawErrors);
3239
+ const {
3240
+ units: optionUnits,
3241
+ precision,
3242
+ step,
3243
+ unitPosition: unitPositionOpt
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
+ );
3257
+ const valueRef = useRef4(value);
3258
+ valueRef.current = value;
3259
+ const handleBlur = useCallback6(() => {
3260
+ onBlur?.(id, valueRef.current);
3261
+ }, [id, onBlur]);
3262
+ return /* @__PURE__ */ jsx28(
3263
+ FormQuantityInput,
3264
+ {
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
+ };
2673
4280
  }
2674
- function parseQuantityOptions(options) {
2675
- const units = Array.isArray(options.units) ? options.units.filter(isUnitOption) : [];
2676
- const precision = typeof options.precision === "number" ? options.precision : void 0;
2677
- const step = typeof options.step === "number" ? options.step : void 0;
2678
- const unitPosition = options.unitPosition === "prefix" || options.unitPosition === "suffix" ? options.unitPosition : void 0;
2679
- return { units, precision, step, unitPosition };
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
+ );
2680
4375
  }
2681
- function QuantityWidget({
2682
- id,
2683
- name,
2684
- value,
2685
- onChange,
2686
- onBlur,
2687
- propertySchema,
2688
- required,
2689
- disabled,
2690
- readonly,
2691
- rawErrors,
2692
- label,
2693
- description,
2694
- placeholder,
2695
- autofocus,
2696
- options = {}
4376
+ function FirstList({
4377
+ props,
4378
+ lists,
4379
+ coding,
4380
+ set,
4381
+ blur
2697
4382
  }) {
2698
- const error = firstError(rawErrors);
2699
- const {
2700
- units,
2701
- precision,
2702
- step,
2703
- unitPosition: unitPositionOpt
2704
- } = parseQuantityOptions(options);
2705
- const quantityValue = value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2706
- const valueRef = useRef4(value);
2707
- valueRef.current = value;
2708
- const handleBlur = useCallback6(() => {
2709
- onBlur?.(id, valueRef.current);
2710
- }, [id, onBlur]);
2711
- return /* @__PURE__ */ jsx25(
2712
- FormQuantityInput,
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,
2713
4387
  {
2714
- id,
2715
- name,
2716
- value: quantityValue,
2717
- onChange,
2718
- onBlur: handleBlur,
2719
- units,
2720
- unitPosition: unitPositionOpt,
2721
- precision,
2722
- step,
2723
- min: propertySchema.minimum,
2724
- max: propertySchema.maximum,
2725
- label,
2726
- labelPlacement: options?.labelPlacement,
2727
- description,
2728
- error,
2729
- placeholder,
2730
- required,
2731
- disabled,
2732
- readOnly: readonly,
2733
- autoFocus: autofocus
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
2734
4396
  }
2735
4397
  );
2736
4398
  }
2737
4399
 
2738
4400
  // src/widgets/DateWidget.tsx
2739
4401
  import { DateInput } from "@petrarca/sonnet-ui";
2740
- import { jsx as jsx26 } from "react/jsx-runtime";
4402
+ import { jsx as jsx33 } from "react/jsx-runtime";
2741
4403
  function resolvePrecision(value) {
2742
4404
  return value === "year" || value === "month" ? value : "day";
2743
4405
  }
@@ -2752,12 +4414,13 @@ function DateWidget({
2752
4414
  label,
2753
4415
  description,
2754
4416
  placeholder,
2755
- options
4417
+ options,
4418
+ propertySchema
2756
4419
  }) {
2757
4420
  const error = firstError(rawErrors);
2758
4421
  const precision = resolvePrecision(options?.precision);
2759
4422
  const locale = typeof options?.locale === "string" ? options.locale : void 0;
2760
- return /* @__PURE__ */ jsx26(
4423
+ return /* @__PURE__ */ jsx33(
2761
4424
  FormFieldWrapper,
2762
4425
  {
2763
4426
  inputId: id,
@@ -2765,7 +4428,7 @@ function DateWidget({
2765
4428
  description,
2766
4429
  required,
2767
4430
  labelPlacement: options?.labelPlacement,
2768
- children: /* @__PURE__ */ jsx26(
4431
+ children: /* @__PURE__ */ jsx33(
2769
4432
  DateInput,
2770
4433
  {
2771
4434
  id,
@@ -2777,7 +4440,9 @@ function DateWidget({
2777
4440
  placeholder,
2778
4441
  required,
2779
4442
  disabled,
2780
- error
4443
+ error,
4444
+ min: propertySchema.formatMinimum,
4445
+ max: propertySchema.formatMaximum
2781
4446
  }
2782
4447
  )
2783
4448
  }
@@ -2786,7 +4451,7 @@ function DateWidget({
2786
4451
 
2787
4452
  // src/widgets/TimeWidget.tsx
2788
4453
  import { TimeInput } from "@petrarca/sonnet-ui";
2789
- import { jsx as jsx27 } from "react/jsx-runtime";
4454
+ import { jsx as jsx34 } from "react/jsx-runtime";
2790
4455
  function resolveGranularity(value) {
2791
4456
  return value === "second" ? "second" : "minute";
2792
4457
  }
@@ -2805,7 +4470,7 @@ function TimeWidget({
2805
4470
  }) {
2806
4471
  const error = firstError(rawErrors);
2807
4472
  const granularity = resolveGranularity(options?.granularity);
2808
- return /* @__PURE__ */ jsx27(
4473
+ return /* @__PURE__ */ jsx34(
2809
4474
  FormFieldWrapper,
2810
4475
  {
2811
4476
  inputId: id,
@@ -2813,7 +4478,7 @@ function TimeWidget({
2813
4478
  description,
2814
4479
  required,
2815
4480
  labelPlacement: options?.labelPlacement,
2816
- children: /* @__PURE__ */ jsx27(
4481
+ children: /* @__PURE__ */ jsx34(
2817
4482
  TimeInput,
2818
4483
  {
2819
4484
  id,
@@ -2833,7 +4498,7 @@ function TimeWidget({
2833
4498
 
2834
4499
  // src/widgets/DateTimeWidget.tsx
2835
4500
  import { DateTimeInput } from "@petrarca/sonnet-ui";
2836
- import { jsx as jsx28 } from "react/jsx-runtime";
4501
+ import { jsx as jsx35 } from "react/jsx-runtime";
2837
4502
  function resolveGranularity2(value) {
2838
4503
  return value === "second" ? "second" : "minute";
2839
4504
  }
@@ -2847,13 +4512,14 @@ function DateTimeWidget({
2847
4512
  rawErrors,
2848
4513
  label,
2849
4514
  description,
2850
- options
4515
+ options,
4516
+ propertySchema
2851
4517
  }) {
2852
4518
  const error = firstError(rawErrors);
2853
4519
  const granularity = resolveGranularity2(options?.granularity);
2854
4520
  const timeZone = typeof options?.timeZone === "string" ? options.timeZone : void 0;
2855
4521
  const locale = typeof options?.locale === "string" ? options.locale : void 0;
2856
- return /* @__PURE__ */ jsx28(
4522
+ return /* @__PURE__ */ jsx35(
2857
4523
  FormFieldWrapper,
2858
4524
  {
2859
4525
  inputId: id,
@@ -2861,7 +4527,7 @@ function DateTimeWidget({
2861
4527
  description,
2862
4528
  required,
2863
4529
  labelPlacement: options?.labelPlacement,
2864
- children: /* @__PURE__ */ jsx28(
4530
+ children: /* @__PURE__ */ jsx35(
2865
4531
  DateTimeInput,
2866
4532
  {
2867
4533
  idPrefix: id,
@@ -2873,7 +4539,9 @@ function DateTimeWidget({
2873
4539
  locale,
2874
4540
  required,
2875
4541
  disabled,
2876
- error
4542
+ error,
4543
+ min: propertySchema.formatMinimum,
4544
+ max: propertySchema.formatMaximum
2877
4545
  }
2878
4546
  )
2879
4547
  }
@@ -2881,20 +4549,20 @@ function DateTimeWidget({
2881
4549
  }
2882
4550
 
2883
4551
  // src/widgets/DisplayWidget.tsx
2884
- import { jsx as jsx29, jsxs as jsxs17 } from "react/jsx-runtime";
4552
+ import { jsx as jsx36, jsxs as jsxs22 } from "react/jsx-runtime";
2885
4553
  function DisplayWidget({
2886
4554
  label,
2887
4555
  description
2888
4556
  }) {
2889
4557
  if (!label && !description) return null;
2890
- return /* @__PURE__ */ jsxs17("div", { className: "space-y-1", children: [
2891
- label && /* @__PURE__ */ jsx29("p", { className: "text-sm text-foreground", children: label }),
2892
- description && /* @__PURE__ */ jsx29("p", { className: "text-sm text-muted-foreground", children: description })
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 })
2893
4561
  ] });
2894
4562
  }
2895
4563
 
2896
4564
  // src/widgets/OneOfWidget.tsx
2897
- import { useMemo as useMemo6 } from "react";
4565
+ import { useMemo as useMemo8 } from "react";
2898
4566
  import {
2899
4567
  getDiscriminator,
2900
4568
  getBranchValue,
@@ -2909,7 +4577,7 @@ import {
2909
4577
  SelectContent,
2910
4578
  SelectItem
2911
4579
  } from "@petrarca/sonnet-ui";
2912
- import { jsx as jsx30, jsxs as jsxs18 } from "react/jsx-runtime";
4580
+ import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
2913
4581
  function buildChoices(branches, discriminator) {
2914
4582
  return branches.map((branch, index) => {
2915
4583
  const raw = discriminator ? getBranchValue(branch, discriminator) : void 0;
@@ -2945,15 +4613,20 @@ function BranchSelector({
2945
4613
  disabled,
2946
4614
  onSelect
2947
4615
  }) {
2948
- return /* @__PURE__ */ jsxs18("div", { className: "space-y-1", children: [
2949
- label && /* @__PURE__ */ jsx30(Label, { htmlFor: id, children: label }),
2950
- /* @__PURE__ */ jsxs18(Select, { value: activeValue, onValueChange: onSelect, disabled, children: [
2951
- /* @__PURE__ */ jsx30(SelectTrigger, { id, children: /* @__PURE__ */ jsx30(SelectValue, { placeholder: "Select an option" }) }),
2952
- /* @__PURE__ */ jsx30(SelectContent, { children: choices.map((c) => /* @__PURE__ */ jsx30(SelectItem, { value: c.value, children: c.label }, c.value)) })
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)) })
2953
4621
  ] }),
2954
- description && /* @__PURE__ */ jsx30("p", { className: "text-sm text-muted-foreground", children: description })
4622
+ description && /* @__PURE__ */ jsx37("p", { className: "text-sm text-muted-foreground", children: description })
2955
4623
  ] });
2956
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
+ }
2957
4630
  function resolveActive(propertySchema, branches, choices, discriminator, data) {
2958
4631
  const index = findActiveBranch(propertySchema, data, discriminator);
2959
4632
  if (index < 0) return { value: "", label: "", subSchema: null };
@@ -2978,12 +4651,12 @@ function OneOfWidget({
2978
4651
  }) {
2979
4652
  const { renderer: Renderer, templates } = useNestedFormContext();
2980
4653
  const ContainerTemplate = templates.ObjectContainerTemplate ?? DefaultObjectContainerTemplate;
2981
- const branches = useMemo6(() => propertySchema.oneOf ?? [], [propertySchema]);
2982
- const discriminator = useMemo6(
4654
+ const branches = useMemo8(() => propertySchema.oneOf ?? [], [propertySchema]);
4655
+ const discriminator = useMemo8(
2983
4656
  () => getDiscriminator(propertySchema),
2984
4657
  [propertySchema]
2985
4658
  );
2986
- const choices = useMemo6(
4659
+ const choices = useMemo8(
2987
4660
  () => buildChoices(branches, discriminator),
2988
4661
  [branches, discriminator]
2989
4662
  );
@@ -2996,9 +4669,9 @@ function OneOfWidget({
2996
4669
  data
2997
4670
  );
2998
4671
  if (branches.length === 0) return null;
2999
- const subUiSchema = uiSchema?.["x-ui-fields"] ?? {};
3000
- return /* @__PURE__ */ jsxs18("div", { className: "space-y-4", children: [
3001
- /* @__PURE__ */ jsx30(
4672
+ const subUiSchema = branchUiSchema(uiSchema, discriminator);
4673
+ return /* @__PURE__ */ jsxs23("div", { className: "space-y-4", children: [
4674
+ /* @__PURE__ */ jsx37(
3002
4675
  BranchSelector,
3003
4676
  {
3004
4677
  id,
@@ -3010,14 +4683,14 @@ function OneOfWidget({
3010
4683
  onSelect: (selected) => onChange(branchChangeData(choices, branches, discriminator, selected))
3011
4684
  }
3012
4685
  ),
3013
- active.subSchema && /* @__PURE__ */ jsx30(
4686
+ active.subSchema && /* @__PURE__ */ jsx37(
3014
4687
  ContainerTemplate,
3015
4688
  {
3016
4689
  label: active.label,
3017
4690
  description: void 0,
3018
4691
  variant: options?.variant || "section",
3019
4692
  defaultOpen: true,
3020
- children: /* @__PURE__ */ jsx30(
4693
+ children: /* @__PURE__ */ jsx37(
3021
4694
  Renderer,
3022
4695
  {
3023
4696
  schema: active.subSchema,
@@ -3035,8 +4708,304 @@ function OneOfWidget({
3035
4708
  ] });
3036
4709
  }
3037
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
+
3038
5006
  // src/widgets/widgetResolver.ts
3039
5007
  import {
5008
+ bindingOf as bindingOf3,
3040
5009
  isNullableBoolean as isNullableBoolean2,
3041
5010
  isOneOf,
3042
5011
  extractEnumValues as extractEnumValues2,
@@ -3113,20 +5082,45 @@ function resolveFromUiWidget(uiWidget, propertySchema, registry) {
3113
5082
  function isObjectArraySchema(schema) {
3114
5083
  return schema.type === "array" && schema.items?.type === "object" && !!schema.items?.properties;
3115
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
+ }
3116
5098
  function registryLookup(registry, widgetName) {
3117
5099
  return registry[widgetName] || registry["TextInputWidget"];
3118
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
+ }
3119
5112
  function resolveFromSchemaStructure(schema, registry) {
3120
5113
  if (isOneOf(schema)) return registryLookup(registry, "OneOfWidget");
3121
5114
  if (isNullableBoolean2(schema))
3122
5115
  return registryLookup(registry, "CheckboxWidget");
3123
5116
  if (extractEnumValues2(schema).length > 0)
3124
5117
  return registryLookup(registry, "SelectWidget");
3125
- if (schema.type === "object" && schema.properties)
3126
- return registryLookup(registry, "ObjectWidget");
3127
- if (isObjectArraySchema(schema))
3128
- return registryLookup(registry, "ArrayWidget");
3129
- return null;
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;
3130
5124
  }
3131
5125
  function resolveFromTypeFormat(schema, registry) {
3132
5126
  const type = getFieldType(schema);
@@ -3157,15 +5151,22 @@ var DEFAULT_WIDGETS = {
3157
5151
  SelectWidget,
3158
5152
  TagsInputWidget,
3159
5153
  JsonEditorWidget,
5154
+ KeyValueWidget,
3160
5155
  EntitySelectWidget,
3161
5156
  ObjectWidget,
3162
5157
  ArrayWidget,
3163
5158
  QuantityWidget,
5159
+ CodedValueWidget,
5160
+ CodeableConceptWidget,
3164
5161
  DateWidget,
3165
5162
  TimeWidget,
3166
5163
  DateTimeWidget,
3167
5164
  DisplayWidget,
3168
5165
  OneOfWidget,
5166
+ RadioWidget,
5167
+ CheckboxGroupWidget,
5168
+ ChoiceWithTextWidget,
5169
+ AbsentReasonsWidget,
3169
5170
  // Lowercase aliases for x-ui-widget convenience
3170
5171
  text: TextInputWidget,
3171
5172
  textarea: TextareaWidget,
@@ -3174,21 +5175,28 @@ var DEFAULT_WIDGETS = {
3174
5175
  select: SelectWidget,
3175
5176
  tags: TagsInputWidget,
3176
5177
  "json-editor": JsonEditorWidget,
5178
+ "key-value": KeyValueWidget,
3177
5179
  "entity-select": EntitySelectWidget,
3178
5180
  object: ObjectWidget,
3179
5181
  array: ArrayWidget,
3180
5182
  quantity: QuantityWidget,
5183
+ "coded-value": CodedValueWidget,
5184
+ "codeable-concept": CodeableConceptWidget,
3181
5185
  date: DateWidget,
3182
5186
  time: TimeWidget,
3183
5187
  "date-time": DateTimeWidget,
3184
5188
  display: DisplayWidget,
3185
- "one-of": OneOfWidget
5189
+ "one-of": OneOfWidget,
5190
+ radio: RadioWidget,
5191
+ "checkbox-group": CheckboxGroupWidget,
5192
+ "choice-with-text": ChoiceWithTextWidget,
5193
+ "absent-reasons": AbsentReasonsWidget
3186
5194
  };
3187
5195
 
3188
5196
  // src/templates/HorizontalArrayItemTemplate.tsx
3189
5197
  import { Trash2 as Trash22, ChevronUp as ChevronUp2, ChevronDown as ChevronDown4 } from "lucide-react";
3190
- import { Button as Button6 } from "@petrarca/sonnet-ui";
3191
- import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
5198
+ import { Button as Button10 } from "@petrarca/sonnet-ui";
5199
+ import { jsx as jsx42, jsxs as jsxs27 } from "react/jsx-runtime";
3192
5200
  function HorizontalArrayItemTemplate({
3193
5201
  children,
3194
5202
  index,
@@ -3212,7 +5220,7 @@ function HorizontalArrayItemTemplate({
3212
5220
  );
3213
5221
  const gap = resolvedLayout.gap ?? 8;
3214
5222
  const alignItems = resolvedLayout.compact ? "center" : "start";
3215
- return /* @__PURE__ */ jsxs19(
5223
+ return /* @__PURE__ */ jsxs27(
3216
5224
  "div",
3217
5225
  {
3218
5226
  "data-fid": fid,
@@ -3224,9 +5232,9 @@ function HorizontalArrayItemTemplate({
3224
5232
  },
3225
5233
  children: [
3226
5234
  children,
3227
- orderable && /* @__PURE__ */ jsxs19("div", { className: "flex flex-col items-center justify-center", children: [
3228
- /* @__PURE__ */ jsx31(
3229
- Button6,
5235
+ orderable && /* @__PURE__ */ jsxs27("div", { className: "flex flex-col items-center justify-center", children: [
5236
+ /* @__PURE__ */ jsx42(
5237
+ Button10,
3230
5238
  {
3231
5239
  type: "button",
3232
5240
  variant: "ghost",
@@ -3235,11 +5243,11 @@ function HorizontalArrayItemTemplate({
3235
5243
  onClick: onMoveUp,
3236
5244
  title: "Move up",
3237
5245
  className: "h-4 w-8 p-0",
3238
- children: /* @__PURE__ */ jsx31(ChevronUp2, { className: "h-3 w-3" })
5246
+ children: /* @__PURE__ */ jsx42(ChevronUp2, { className: "h-3 w-3" })
3239
5247
  }
3240
5248
  ),
3241
- /* @__PURE__ */ jsx31(
3242
- Button6,
5249
+ /* @__PURE__ */ jsx42(
5250
+ Button10,
3243
5251
  {
3244
5252
  type: "button",
3245
5253
  variant: "ghost",
@@ -3248,12 +5256,12 @@ function HorizontalArrayItemTemplate({
3248
5256
  onClick: onMoveDown,
3249
5257
  title: "Move down",
3250
5258
  className: "h-4 w-8 p-0",
3251
- children: /* @__PURE__ */ jsx31(ChevronDown4, { className: "h-3 w-3" })
5259
+ children: /* @__PURE__ */ jsx42(ChevronDown4, { className: "h-3 w-3" })
3252
5260
  }
3253
5261
  )
3254
5262
  ] }),
3255
- canRemove && /* @__PURE__ */ jsx31(
3256
- Button6,
5263
+ canRemove && /* @__PURE__ */ jsx42(
5264
+ Button10,
3257
5265
  {
3258
5266
  type: "button",
3259
5267
  variant: "ghost",
@@ -3262,7 +5270,7 @@ function HorizontalArrayItemTemplate({
3262
5270
  onClick: onRemove,
3263
5271
  title: "Remove item",
3264
5272
  className: "text-muted-foreground hover:text-destructive h-8 w-8 p-0",
3265
- children: /* @__PURE__ */ jsx31(Trash22, { className: "h-3.5 w-3.5" })
5273
+ children: /* @__PURE__ */ jsx42(Trash22, { className: "h-3.5 w-3.5" })
3266
5274
  }
3267
5275
  )
3268
5276
  ]
@@ -3271,49 +5279,49 @@ function HorizontalArrayItemTemplate({
3271
5279
  }
3272
5280
 
3273
5281
  // src/templates/TabsGroupTemplate.tsx
3274
- import { useState as useState7 } from "react";
5282
+ import { useState as useState14 } from "react";
3275
5283
  import { Tabs, TabsList, TabsTrigger, TabsContent } from "@petrarca/sonnet-ui";
3276
5284
 
3277
5285
  // src/templates/GroupErrorBadge.tsx
3278
5286
  import { AlertCircle } from "lucide-react";
3279
- import { jsx as jsx32 } from "react/jsx-runtime";
5287
+ import { jsx as jsx43 } from "react/jsx-runtime";
3280
5288
  function GroupErrorBadge({
3281
5289
  count
3282
5290
  }) {
3283
5291
  if (count === 0) return null;
3284
5292
  const label = count === 1 ? "1 field needs attention" : `${count} fields need attention`;
3285
- return /* @__PURE__ */ jsx32(
5293
+ return /* @__PURE__ */ jsx43(
3286
5294
  "span",
3287
5295
  {
3288
5296
  role: "img",
3289
5297
  "aria-label": label,
3290
5298
  title: label,
3291
5299
  className: "inline-flex shrink-0 text-destructive",
3292
- children: /* @__PURE__ */ jsx32(AlertCircle, { className: "h-3.5 w-3.5" })
5300
+ children: /* @__PURE__ */ jsx43(AlertCircle, { className: "h-3.5 w-3.5" })
3293
5301
  }
3294
5302
  );
3295
5303
  }
3296
5304
 
3297
5305
  // src/templates/TabsGroupTemplate.tsx
3298
- import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
5306
+ import { jsx as jsx44, jsxs as jsxs28 } from "react/jsx-runtime";
3299
5307
  function TabsGroupTemplate({
3300
5308
  groups
3301
5309
  }) {
3302
- const [active, setActive] = useState7(groups[0].key);
5310
+ const [active, setActive] = useState14(groups[0].key);
3303
5311
  const value = groups.some((g) => g.key === active) ? active : groups[0].key;
3304
- return /* @__PURE__ */ jsxs20(Tabs, { value, onValueChange: setActive, children: [
3305
- /* @__PURE__ */ jsx33(TabsList, { className: "mb-4", children: groups.map((g) => /* @__PURE__ */ jsxs20(TabsTrigger, { value: g.key, className: "gap-1.5", children: [
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: [
3306
5314
  g.label,
3307
- /* @__PURE__ */ jsx33(GroupErrorBadge, { count: g.errorCount })
5315
+ /* @__PURE__ */ jsx44(GroupErrorBadge, { count: g.errorCount })
3308
5316
  ] }, g.key)) }),
3309
- groups.map((g) => /* @__PURE__ */ jsx33(TabsContent, { value: g.key, className: "space-y-4", children: g.content }, g.key))
5317
+ groups.map((g) => /* @__PURE__ */ jsx44(TabsContent, { value: g.key, className: "space-y-4", children: g.content }, g.key))
3310
5318
  ] });
3311
5319
  }
3312
5320
 
3313
5321
  // src/templates/StepperGroupTemplate.tsx
3314
- import { useState as useState8 } from "react";
5322
+ import { useState as useState15 } from "react";
3315
5323
  import {
3316
- Button as Button7,
5324
+ Button as Button11,
3317
5325
  Stepper,
3318
5326
  StepperList,
3319
5327
  StepperItem,
@@ -3325,47 +5333,47 @@ import {
3325
5333
  StepperPrev,
3326
5334
  StepperNext
3327
5335
  } from "@petrarca/sonnet-ui";
3328
- import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
5336
+ import { jsx as jsx45, jsxs as jsxs29 } from "react/jsx-runtime";
3329
5337
  function StepperGroupTemplate({
3330
5338
  groups
3331
5339
  }) {
3332
- const [active, setActive] = useState8(groups[0].key);
5340
+ const [active, setActive] = useState15(groups[0].key);
3333
5341
  const value = groups.some((g) => g.key === active) ? active : groups[0].key;
3334
5342
  const isLast = value === groups[groups.length - 1].key;
3335
- return /* @__PURE__ */ jsxs21(Stepper, { value, onValueChange: setActive, children: [
3336
- /* @__PURE__ */ jsx34(StepperList, { className: "mb-4 gap-2", children: groups.map((g) => /* @__PURE__ */ jsxs21(StepperItem, { value: g.key, children: [
3337
- /* @__PURE__ */ jsxs21(StepperTrigger, { children: [
3338
- /* @__PURE__ */ jsx34(StepperIndicator, {}),
3339
- /* @__PURE__ */ jsxs21(StepperTitle, { className: "inline-flex items-center gap-1.5", children: [
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: [
3340
5348
  g.label,
3341
- /* @__PURE__ */ jsx34(GroupErrorBadge, { count: g.errorCount })
5349
+ /* @__PURE__ */ jsx45(GroupErrorBadge, { count: g.errorCount })
3342
5350
  ] })
3343
5351
  ] }),
3344
- /* @__PURE__ */ jsx34(StepperSeparator, {})
5352
+ /* @__PURE__ */ jsx45(StepperSeparator, {})
3345
5353
  ] }, g.key)) }),
3346
- groups.map((g) => /* @__PURE__ */ jsx34(StepperContent, { value: g.key, className: "space-y-4", children: g.content }, g.key)),
3347
- /* @__PURE__ */ jsxs21("div", { className: "mt-4 flex justify-between", children: [
3348
- /* @__PURE__ */ jsx34(StepperPrev, { asChild: true, children: /* @__PURE__ */ jsx34(Button7, { variant: "outline", size: "sm", children: "Back" }) }),
3349
- !isLast && /* @__PURE__ */ jsx34(StepperNext, { asChild: true, children: /* @__PURE__ */ jsx34(Button7, { size: "sm", children: "Next" }) })
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" }) })
3350
5358
  ] })
3351
5359
  ] });
3352
5360
  }
3353
5361
 
3354
5362
  // src/templates/SectionsGroupTemplate.tsx
3355
- import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
5363
+ import { jsx as jsx46, jsxs as jsxs30 } from "react/jsx-runtime";
3356
5364
  function SectionsGroupTemplate({
3357
5365
  groups
3358
5366
  }) {
3359
- return /* @__PURE__ */ jsx35("div", { className: "space-y-6", children: groups.map((g) => /* @__PURE__ */ jsxs22(
5367
+ return /* @__PURE__ */ jsx46("div", { className: "space-y-6", children: groups.map((g) => /* @__PURE__ */ jsxs30(
3360
5368
  "fieldset",
3361
5369
  {
3362
5370
  className: "rounded-md border border-border/60 px-3 pb-3 pt-1",
3363
5371
  children: [
3364
- /* @__PURE__ */ jsxs22("legend", { className: "inline-flex items-center gap-1.5 px-1 text-sm font-medium text-muted-foreground", children: [
5372
+ /* @__PURE__ */ jsxs30("legend", { className: "inline-flex items-center gap-1.5 px-1 text-sm font-medium text-muted-foreground", children: [
3365
5373
  g.label,
3366
- /* @__PURE__ */ jsx35(GroupErrorBadge, { count: g.errorCount })
5374
+ /* @__PURE__ */ jsx46(GroupErrorBadge, { count: g.errorCount })
3367
5375
  ] }),
3368
- /* @__PURE__ */ jsx35("div", { className: "space-y-4", children: g.content })
5376
+ /* @__PURE__ */ jsx46("div", { className: "space-y-4", children: g.content })
3369
5377
  ]
3370
5378
  },
3371
5379
  g.key
@@ -3389,13 +5397,15 @@ function groupTemplateKey(variant) {
3389
5397
  }
3390
5398
 
3391
5399
  // src/FormFieldRenderer.tsx
5400
+ import { useMemo as useMemo9 } from "react";
3392
5401
  import { warnLog } from "@petrarca/sonnet-core";
3393
5402
  import {
3394
5403
  getFieldTitle,
3395
5404
  getFieldHelpText,
3396
- getFieldPlaceholder
5405
+ getFieldPlaceholder,
5406
+ liftNullableMember
3397
5407
  } from "@petrarca/sonnet-core/schema";
3398
- import { jsx as jsx36 } from "react/jsx-runtime";
5408
+ import { jsx as jsx47 } from "react/jsx-runtime";
3399
5409
  function mergeFieldOptions(globalOptions, property, uiSchema) {
3400
5410
  const xWidgetOptions = property["x-ui-options"] || {};
3401
5411
  const fieldOptions = uiSchema?.["x-ui-options"] || {};
@@ -3422,7 +5432,7 @@ function applyFieldWidth(widget, uiSchema, mergedOptions) {
3422
5432
  }
3423
5433
  const width = candidate && candidate in FIELD_WIDTH_CLASS ? candidate : "full";
3424
5434
  if (width === "full") return widget;
3425
- return /* @__PURE__ */ jsx36("div", { className: FIELD_WIDTH_CLASS[width], children: widget });
5435
+ return /* @__PURE__ */ jsx47("div", { className: FIELD_WIDTH_CLASS[width], children: widget });
3426
5436
  }
3427
5437
  function resolvePresentationText(property, uiSchema) {
3428
5438
  return {
@@ -3438,7 +5448,7 @@ function resolveFieldFlags(fieldOptions, globalOptions, disabled, readOnly) {
3438
5448
  }
3439
5449
  function FormFieldRenderer({
3440
5450
  fieldName,
3441
- property,
5451
+ property: declared,
3442
5452
  value,
3443
5453
  onChange,
3444
5454
  onBlur,
@@ -3453,6 +5463,7 @@ function FormFieldRenderer({
3453
5463
  idPrefix = "field",
3454
5464
  rawErrors
3455
5465
  }) {
5466
+ const property = useMemo9(() => liftNullableMember(declared), [declared]);
3456
5467
  const label = getFieldTitle(fieldName, property);
3457
5468
  const { description, placeholder } = resolvePresentationText(
3458
5469
  property,
@@ -3473,7 +5484,7 @@ function FormFieldRenderer({
3473
5484
  uiSchema?.["x-ui-title"],
3474
5485
  globalOptions.label
3475
5486
  );
3476
- const widget = /* @__PURE__ */ jsx36(
5487
+ const widget = /* @__PURE__ */ jsx47(
3477
5488
  Widget,
3478
5489
  {
3479
5490
  id: `${idPrefix}-${fieldName}`,
@@ -3500,15 +5511,17 @@ function FormFieldRenderer({
3500
5511
  }
3501
5512
 
3502
5513
  // src/hooks/useFormEngine.ts
3503
- import { useState as useState10, useEffect as useEffect4, useMemo as useMemo8, useCallback as useCallback7, useRef as useRef6 } from "react";
5514
+ import { useState as useState17, useEffect as useEffect8, useMemo as useMemo11, useCallback as useCallback8, useRef as useRef8 } from "react";
3504
5515
  import { isEqual } from "@petrarca/sonnet-core";
3505
5516
  import {
5517
+ isNullableBoolean as isNullableBoolean3,
3506
5518
  getSchemaProperties as getSchemaProperties4,
3507
5519
  getFieldDefault,
3508
5520
  applySchemaDefaults as applySchemaDefaults2,
3509
5521
  resolveSchema,
3510
5522
  resolveRefs,
3511
5523
  validateFormData,
5524
+ stripFormItemIds,
3512
5525
  mergeFieldErrors,
3513
5526
  diffFormData,
3514
5527
  ensureFormItemIds
@@ -3601,7 +5614,7 @@ function runValidators(schema, data, registry, uiSchema, includeFormValidators)
3601
5614
  }
3602
5615
 
3603
5616
  // src/validators/useAsyncValidators.ts
3604
- import { useEffect as useEffect3, useMemo as useMemo7, useRef as useRef5, useState as useState9 } from "react";
5617
+ import { useEffect as useEffect7, useMemo as useMemo10, useRef as useRef7, useState as useState16 } from "react";
3605
5618
  import { getSchemaProperties as getSchemaProperties3 } from "@petrarca/sonnet-core/schema";
3606
5619
  function keyOf(value) {
3607
5620
  try {
@@ -3657,12 +5670,12 @@ function collectCandidates(schema, data, registry, uiSchema, isRoot) {
3657
5670
  return out;
3658
5671
  }
3659
5672
  function useAsyncValidators(schema, data, registry, uiSchema, isRoot) {
3660
- const [entries, setEntries] = useState9({});
3661
- const handledKey = useRef5({});
5673
+ const [entries, setEntries] = useState16({});
5674
+ const handledKey = useRef7({});
3662
5675
  const candidates = registry && (registry.asyncField || registry.asyncForm) ? collectCandidates(schema, data, registry, uiSchema, isRoot) : [];
3663
5676
  const liveIds = new Set(candidates.map((c) => c.id));
3664
5677
  const signature = candidates.map((c) => `${c.id}=${c.key}`).join(";");
3665
- useEffect3(() => {
5678
+ useEffect7(() => {
3666
5679
  for (const c of candidates) {
3667
5680
  if (handledKey.current[c.id] === c.key) continue;
3668
5681
  handledKey.current[c.id] = c.key;
@@ -3681,7 +5694,7 @@ function useAsyncValidators(schema, data, registry, uiSchema, isRoot) {
3681
5694
  }
3682
5695
  }, [signature]);
3683
5696
  const liveKey = [...liveIds].sort().join(",");
3684
- useEffect3(() => {
5697
+ useEffect7(() => {
3685
5698
  setEntries((e) => {
3686
5699
  const kept = Object.entries(e).filter(([id]) => liveIds.has(id));
3687
5700
  return kept.length === Object.keys(e).length ? e : Object.fromEntries(kept);
@@ -3701,7 +5714,7 @@ function useAsyncValidators(schema, data, registry, uiSchema, isRoot) {
3701
5714
  errorList.push(...entry.errors);
3702
5715
  }
3703
5716
  const errorSig = errorList.map((e) => `${e.pointer}\0${e.message}`).join("|");
3704
- const asyncErrors = useMemo7(() => errorList, [errorSig]);
5717
+ const asyncErrors = useMemo10(() => errorList, [errorSig]);
3705
5718
  return { asyncErrors, isValidating };
3706
5719
  }
3707
5720
 
@@ -3721,9 +5734,14 @@ function orderProperties(properties, orderArray) {
3721
5734
  }
3722
5735
 
3723
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
+ }
3724
5741
  function isImmediateField(fieldName, properties) {
3725
5742
  const property = properties[fieldName];
3726
- return property?.type === "boolean" || property?.enum !== void 0;
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;
3727
5745
  }
3728
5746
  function computeBaselineUpdate(newData, originalData, initialized, schemaPropertyNames) {
3729
5747
  const newCount = Object.keys(newData).length;
@@ -3750,7 +5768,7 @@ function computeBaselineUpdate(newData, originalData, initialized, schemaPropert
3750
5768
  }
3751
5769
  function computeAllErrors(args) {
3752
5770
  const { schema, data, customSync, asyncErrors, extraErrors } = args;
3753
- const base = validateFormData(schema, data).fieldErrors;
5771
+ const base = validateFormData(schema, stripFormItemIds(data)).fieldErrors;
3754
5772
  return mergeFieldErrors(base, customSync, asyncErrors, extraErrors ?? []);
3755
5773
  }
3756
5774
  function useFormEngine({
@@ -3772,21 +5790,21 @@ function useFormEngine({
3772
5790
  validators,
3773
5791
  isRoot = true
3774
5792
  }) {
3775
- const refResolvedSchema = useMemo8(
5793
+ const refResolvedSchema = useMemo11(
3776
5794
  () => resolveRefs(schema, refResolver),
3777
5795
  [schema, refResolver]
3778
5796
  );
3779
- const seed = useState10(
5797
+ const seed = useState17(
3780
5798
  () => ensureFormItemIds(
3781
5799
  applySchemaDefaults2(data, refResolvedSchema),
3782
5800
  refResolvedSchema
3783
5801
  )
3784
5802
  )[0];
3785
- const [originalData, setOriginalData] = useState10(
5803
+ const [originalData, setOriginalData] = useState17(
3786
5804
  () => seed
3787
5805
  );
3788
- const [originalDataInitialized, setOriginalDataInitialized] = useState10(false);
3789
- const [nonSchemaData, setNonSchemaData] = useState10(
5806
+ const [originalDataInitialized, setOriginalDataInitialized] = useState17(false);
5807
+ const [nonSchemaData, setNonSchemaData] = useState17(
3790
5808
  () => {
3791
5809
  const props = getSchemaProperties4(refResolvedSchema);
3792
5810
  const names = Object.keys(props);
@@ -3795,26 +5813,26 @@ function useFormEngine({
3795
5813
  );
3796
5814
  }
3797
5815
  );
3798
- const [formData, setFormData] = useState10(() => seed);
3799
- const formDataRef = useRef6(formData);
5816
+ const [formData, setFormData] = useState17(() => seed);
5817
+ const formDataRef = useRef8(formData);
3800
5818
  formDataRef.current = formData;
3801
- const [pendingChanges, setPendingChanges] = useState10({});
3802
- const resolvedSchema = useMemo8(
5819
+ const [pendingChanges, setPendingChanges] = useState17({});
5820
+ const resolvedSchema = useMemo11(
3803
5821
  () => resolveSchema(refResolvedSchema, formData),
3804
5822
  [refResolvedSchema, formData]
3805
5823
  );
3806
- const orderedProperties = useMemo8(() => {
5824
+ const orderedProperties = useMemo11(() => {
3807
5825
  const properties2 = getSchemaProperties4(resolvedSchema);
3808
5826
  const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
3809
5827
  const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
3810
5828
  return orderProperties(properties2, orderArray);
3811
5829
  }, [resolvedSchema, uiSchema]);
3812
- const [initialDefaultsSent, setInitialDefaultsSent] = useState10(false);
3813
- const [edited, setEdited] = useState10({});
3814
- const [touched, setTouched] = useState10({});
3815
- const [submitAttempted, setSubmitAttempted] = useState10(false);
5830
+ const [initialDefaultsSent, setInitialDefaultsSent] = useState17(false);
5831
+ const edited = useRef8(/* @__PURE__ */ new Set());
5832
+ const [touched, setTouched] = useState17({});
5833
+ const [submitAttempted, setSubmitAttempted] = useState17(false);
3816
5834
  const properties = getSchemaProperties4(resolvedSchema);
3817
- useEffect4(() => {
5835
+ useEffect8(() => {
3818
5836
  if (!initialDefaultsSent) {
3819
5837
  const hasDefaults = Object.entries(properties).some(
3820
5838
  ([fieldName, property]) => data[fieldName] === void 0 && getFieldDefault(property) !== void 0
@@ -3825,7 +5843,7 @@ function useFormEngine({
3825
5843
  setInitialDefaultsSent(true);
3826
5844
  }
3827
5845
  }, [initialDefaultsSent, properties, data, formData, onChange]);
3828
- useEffect4(() => {
5846
+ useEffect8(() => {
3829
5847
  const currentPropertyNames = Object.keys(properties);
3830
5848
  const keysToRemove = Object.keys(formData).filter(
3831
5849
  (k) => !currentPropertyNames.includes(k)
@@ -3839,8 +5857,23 @@ function useFormEngine({
3839
5857
  if (onChange) onChange(cleanedData);
3840
5858
  }
3841
5859
  }, [properties, formData, onChange]);
3842
- const prevDataRef = useRef6(data);
3843
- useEffect4(() => {
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(() => {
3844
5877
  if (isEqual(prevDataRef.current, data)) return;
3845
5878
  prevDataRef.current = data;
3846
5879
  const newData = ensureFormItemIds(
@@ -3870,17 +5903,17 @@ function useFormEngine({
3870
5903
  originalData,
3871
5904
  originalDataInitialized
3872
5905
  ]);
3873
- const hasChanges = useMemo8(
5906
+ const hasChanges = useMemo11(
3874
5907
  () => !isEqual(formData, originalData),
3875
5908
  [formData, originalData]
3876
5909
  );
3877
- const displayData = useMemo8(() => {
5910
+ const displayData = useMemo11(() => {
3878
5911
  if (deferStateUpdates && Object.keys(pendingChanges).length > 0) {
3879
5912
  return { ...formData, ...pendingChanges };
3880
5913
  }
3881
5914
  return formData;
3882
5915
  }, [formData, pendingChanges, deferStateUpdates]);
3883
- const customSync = useMemo8(
5916
+ const customSync = useMemo11(
3884
5917
  () => runValidators(resolvedSchema, displayData, validators, uiSchema, isRoot),
3885
5918
  [resolvedSchema, displayData, validators, uiSchema, isRoot]
3886
5919
  );
@@ -3891,7 +5924,7 @@ function useFormEngine({
3891
5924
  uiSchema,
3892
5925
  isRoot
3893
5926
  );
3894
- const validation = useMemo8(() => {
5927
+ const validation = useMemo11(() => {
3895
5928
  const merged = computeAllErrors({
3896
5929
  schema: resolvedSchema,
3897
5930
  data: displayData,
@@ -3906,17 +5939,15 @@ function useFormEngine({
3906
5939
  };
3907
5940
  }, [resolvedSchema, displayData, customSync, asyncErrors, extraErrors]);
3908
5941
  const { isValid, fieldErrors } = validation;
3909
- const onValidationChangeRef = useRef6(onValidationChange);
5942
+ const onValidationChangeRef = useRef8(onValidationChange);
3910
5943
  onValidationChangeRef.current = onValidationChange;
3911
- useEffect4(() => {
5944
+ useEffect8(() => {
3912
5945
  onValidationChangeRef.current?.(validation.errors, validation.isValid);
3913
5946
  }, [validation]);
3914
- const handleFieldChange = useCallback7(
5947
+ const handleFieldChange = useCallback8(
3915
5948
  (fieldName, value) => {
3916
5949
  if (readOnly) return;
3917
- setEdited(
3918
- (prev) => prev[fieldName] ? prev : { ...prev, [fieldName]: true }
3919
- );
5950
+ edited.current.add(fieldName);
3920
5951
  const immediate = isImmediateField(fieldName, properties);
3921
5952
  if (deferStateUpdates && !immediate) {
3922
5953
  setPendingChanges((prev) => ({ ...prev, [fieldName]: value }));
@@ -3939,7 +5970,7 @@ function useFormEngine({
3939
5970
  properties
3940
5971
  ]
3941
5972
  );
3942
- const flushPendingField = useCallback7(
5973
+ const flushPendingField = useCallback8(
3943
5974
  (fieldName) => {
3944
5975
  if (!deferStateUpdates) return;
3945
5976
  if (pendingChanges[fieldName] === void 0) return;
@@ -3954,10 +5985,10 @@ function useFormEngine({
3954
5985
  },
3955
5986
  [showActions, deferStateUpdates, pendingChanges, formData, onChange]
3956
5987
  );
3957
- const handleFieldBlur = useCallback7(
5988
+ const handleFieldBlur = useCallback8(
3958
5989
  (fieldName) => {
3959
5990
  if (readOnly) return;
3960
- if (edited[fieldName]) {
5991
+ if (edited.current.has(fieldName)) {
3961
5992
  setTouched(
3962
5993
  (prev) => prev[fieldName] ? prev : { ...prev, [fieldName]: true }
3963
5994
  );
@@ -3975,7 +6006,6 @@ function useFormEngine({
3975
6006
  },
3976
6007
  [
3977
6008
  readOnly,
3978
- edited,
3979
6009
  pendingChanges,
3980
6010
  deferChanges,
3981
6011
  deferStateUpdates,
@@ -3985,7 +6015,7 @@ function useFormEngine({
3985
6015
  flushPendingField
3986
6016
  ]
3987
6017
  );
3988
- const handleSave = useCallback7(() => {
6018
+ const handleSave = useCallback8(() => {
3989
6019
  setSubmitAttempted(true);
3990
6020
  if (!hasChanges && !allowPristineSave) return;
3991
6021
  if (isValidating) return;
@@ -4032,10 +6062,10 @@ function useFormEngine({
4032
6062
  validators,
4033
6063
  isRoot
4034
6064
  ]);
4035
- const handleCancel = useCallback7(() => {
6065
+ const handleCancel = useCallback8(() => {
4036
6066
  setFormData({ ...originalData });
4037
6067
  setPendingChanges({});
4038
- setEdited({});
6068
+ edited.current = /* @__PURE__ */ new Set();
4039
6069
  setTouched({});
4040
6070
  setSubmitAttempted(false);
4041
6071
  onCancel?.();
@@ -4063,30 +6093,7 @@ import {
4063
6093
  useValidationDisplay,
4064
6094
  useSaveWhenInvalid
4065
6095
  } from "@petrarca/sonnet-core/config";
4066
- import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
4067
- function stripFids(data) {
4068
- const result = {};
4069
- for (const [key, value] of Object.entries(data)) {
4070
- if (Array.isArray(value)) {
4071
- result[key] = value.map((item) => {
4072
- if (item !== null && typeof item === "object" && !Array.isArray(item)) {
4073
- const rest = Object.fromEntries(
4074
- Object.entries(item).filter(
4075
- ([k]) => k !== FORM_ITEM_ID_FIELD2
4076
- )
4077
- );
4078
- return stripFids(rest);
4079
- }
4080
- return item;
4081
- });
4082
- } else if (value !== null && typeof value === "object") {
4083
- result[key] = stripFids(value);
4084
- } else {
4085
- result[key] = value;
4086
- }
4087
- }
4088
- return result;
4089
- }
6096
+ import { jsx as jsx48, jsxs as jsxs31 } from "react/jsx-runtime";
4090
6097
  function resolveFlagDefaults(props) {
4091
6098
  return {
4092
6099
  disabled: props.disabled ?? false,
@@ -4108,18 +6115,20 @@ function resolveValueDefaults(props) {
4108
6115
  uiSchema: props.uiSchema ?? {}
4109
6116
  };
4110
6117
  }
4111
- function useWrappedCallbacks(onChange, onBlur, onUpdate) {
4112
- const wrappedOnChange = useCallback8(
4113
- (d) => onChange?.(stripFids(d)),
4114
- [onChange]
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]
4115
6124
  );
4116
- const wrappedOnBlur = useCallback8(
4117
- (fieldName, d) => onBlur?.(fieldName, stripFids(d)),
4118
- [onBlur]
6125
+ const wrappedOnBlur = useCallback9(
6126
+ (fieldName, d) => onBlur?.(fieldName, strip(d)),
6127
+ [onBlur, strip]
4119
6128
  );
4120
- const wrappedOnUpdate = useCallback8(
4121
- (d, changed, diff) => onUpdate?.(stripFids(d), stripFids(changed), diff),
4122
- [onUpdate]
6129
+ const wrappedOnUpdate = useCallback9(
6130
+ (d, changed, diff) => onUpdate?.(strip(d), strip(changed), diff),
6131
+ [onUpdate, strip]
4123
6132
  );
4124
6133
  return {
4125
6134
  wrappedOnChange: onChange ? wrappedOnChange : void 0,
@@ -4130,7 +6139,7 @@ function useWrappedCallbacks(onChange, onBlur, onUpdate) {
4130
6139
  function buildFieldMap(fields) {
4131
6140
  const map = /* @__PURE__ */ new Map();
4132
6141
  for (const field of fields) {
4133
- if (React13.isValidElement(field) && field.key != null) {
6142
+ if (React18.isValidElement(field) && field.key != null) {
4134
6143
  map.set(String(field.key), field);
4135
6144
  }
4136
6145
  }
@@ -4149,12 +6158,12 @@ function resolveGridColumns(row, fieldCount) {
4149
6158
  }
4150
6159
  function wrapInGroup(group, key, content) {
4151
6160
  if (!group) return content;
4152
- return /* @__PURE__ */ jsxs23(
6161
+ return /* @__PURE__ */ jsxs31(
4153
6162
  "fieldset",
4154
6163
  {
4155
6164
  className: "rounded-md border border-border/60 px-3 pb-3 pt-1",
4156
6165
  children: [
4157
- /* @__PURE__ */ jsx37("legend", { className: "px-1 text-sm font-medium text-muted-foreground", children: group }),
6166
+ /* @__PURE__ */ jsx48("legend", { className: "px-1 text-sm font-medium text-muted-foreground", children: group }),
4158
6167
  content
4159
6168
  ]
4160
6169
  },
@@ -4176,7 +6185,7 @@ function buildRowElement(row, fieldMap, defaultRowGap) {
4176
6185
  gridTemplateColumns: resolveGridColumns(row, rowFields.length),
4177
6186
  gap: `${rowGap}px`
4178
6187
  };
4179
- const inner = /* @__PURE__ */ jsx37("div", { style, children: rowFields }, `row:${rowKey}`);
6188
+ const inner = /* @__PURE__ */ jsx48("div", { style, children: rowFields }, `row:${rowKey}`);
4180
6189
  return {
4181
6190
  element: wrapInGroup(row.group, rowKey, inner),
4182
6191
  placed: row.fields
@@ -4185,7 +6194,7 @@ function buildRowElement(row, fieldMap, defaultRowGap) {
4185
6194
  function collectUnplacedFields(fields, placed) {
4186
6195
  const result = [];
4187
6196
  for (const field of fields) {
4188
- if (React13.isValidElement(field) && field.key != null && !placed.has(String(field.key))) {
6197
+ if (React18.isValidElement(field) && field.key != null && !placed.has(String(field.key))) {
4189
6198
  result.push(field);
4190
6199
  }
4191
6200
  }
@@ -4278,8 +6287,8 @@ function HiddenErrorNotice({
4278
6287
  count
4279
6288
  }) {
4280
6289
  if (count === 0) return null;
4281
- return /* @__PURE__ */ jsxs23("p", { className: "flex items-center gap-1.5 text-sm text-destructive", children: [
4282
- /* @__PURE__ */ jsx37(AlertCircle2, { className: "h-4 w-4 shrink-0" }),
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" }),
4283
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.`
4284
6293
  ] });
4285
6294
  }
@@ -4287,13 +6296,13 @@ function FormLevelErrors({
4287
6296
  messages
4288
6297
  }) {
4289
6298
  if (messages.length === 0) return null;
4290
- return /* @__PURE__ */ jsx37("div", { className: "space-y-1", children: messages.map((msg, i) => /* @__PURE__ */ jsxs23(
6299
+ return /* @__PURE__ */ jsx48("div", { className: "space-y-1", children: messages.map((msg, i) => /* @__PURE__ */ jsxs31(
4291
6300
  "p",
4292
6301
  {
4293
6302
  role: "alert",
4294
6303
  className: "flex items-center gap-1.5 text-sm text-destructive",
4295
6304
  children: [
4296
- /* @__PURE__ */ jsx37(AlertCircle2, { className: "h-4 w-4 shrink-0" }),
6305
+ /* @__PURE__ */ jsx48(AlertCircle2, { className: "h-4 w-4 shrink-0" }),
4297
6306
  msg
4298
6307
  ]
4299
6308
  },
@@ -4318,7 +6327,7 @@ function layoutFieldsInGroups(fields, groupDefs, defaultRowGap, fieldErrors, for
4318
6327
  content: b.rows,
4319
6328
  errorCount: groupErrorCount(groupDefs[b.index], fieldErrors)
4320
6329
  }));
4321
- return [/* @__PURE__ */ jsx37(GroupTemplate, { groups }, "form:groups"), ...unplaced];
6330
+ return [/* @__PURE__ */ jsx48(GroupTemplate, { groups }, "form:groups"), ...unplaced];
4322
6331
  }
4323
6332
  function FormContainer({
4324
6333
  displayContents,
@@ -4342,14 +6351,14 @@ function FormContainer({
4342
6351
  children
4343
6352
  }) {
4344
6353
  if (displayContents) {
4345
- return /* @__PURE__ */ jsx37("div", { style: { display: "contents" }, children });
6354
+ return /* @__PURE__ */ jsx48("div", { style: { display: "contents" }, children });
4346
6355
  }
4347
- return /* @__PURE__ */ jsxs23(SimpleStack, { gap: "md", children: [
6356
+ return /* @__PURE__ */ jsxs31(SimpleStack, { gap: "md", children: [
4348
6357
  children,
4349
- showExtraProperties && /* @__PURE__ */ jsx37(ExtraPropertiesCard_default, { properties: formData, schema: resolvedSchema }),
4350
- /* @__PURE__ */ jsx37(HiddenErrorNotice, { count: hiddenErrorCount }),
4351
- /* @__PURE__ */ jsx37(FormLevelErrors, { messages: formLevelErrors }),
4352
- showActions && /* @__PURE__ */ jsx37(
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(
4353
6362
  FormActions,
4354
6363
  {
4355
6364
  onSave: handleSave,
@@ -4395,7 +6404,7 @@ function JsonSchemaFormRenderer(props) {
4395
6404
  allowPristineSave
4396
6405
  } = resolveFlagDefaults(props);
4397
6406
  const { saveLabel, cancelLabel, widgets, uiSchema } = resolveValueDefaults(props);
4398
- const { wrappedOnChange, wrappedOnBlur, wrappedOnUpdate } = useWrappedCallbacks(onChange, onBlur, onUpdate);
6407
+ const { wrappedOnChange, wrappedOnBlur, wrappedOnUpdate } = useWrappedCallbacks(onChange, onBlur, onUpdate, isRootForm);
4399
6408
  const {
4400
6409
  formData,
4401
6410
  displayData,
@@ -4431,19 +6440,19 @@ function JsonSchemaFormRenderer(props) {
4431
6440
  isRoot: isRootForm
4432
6441
  });
4433
6442
  const layoutLabelPlacement = uiSchema["x-ui-layout"]?.labelPlacement;
4434
- const globalOptions = useMemo9(
6443
+ const globalOptions = useMemo12(
4435
6444
  () => ({
4436
6445
  ...layoutLabelPlacement ? { labelPlacement: layoutLabelPlacement } : {},
4437
6446
  ...uiSchema["x-ui-globalOptions"] || {}
4438
6447
  }),
4439
6448
  [layoutLabelPlacement, uiSchema]
4440
6449
  );
4441
- const resolvedTemplates = useMemo9(
6450
+ const resolvedTemplates = useMemo12(
4442
6451
  () => ({ ...DEFAULT_TEMPLATES, ...templates }),
4443
6452
  [templates]
4444
6453
  );
4445
- const nestedRenderer = useCallback8(
4446
- (props2) => /* @__PURE__ */ jsx37(
6454
+ const nestedRenderer = useCallback9(
6455
+ (props2) => /* @__PURE__ */ jsx48(
4447
6456
  JsonSchemaFormRenderer,
4448
6457
  {
4449
6458
  ...props2,
@@ -4460,7 +6469,7 @@ function JsonSchemaFormRenderer(props) {
4460
6469
  nestedFormContext,
4461
6470
  submitAttempted
4462
6471
  );
4463
- const contextValue = useMemo9(
6472
+ const contextValue = useMemo12(
4464
6473
  () => ({
4465
6474
  renderer: nestedRenderer,
4466
6475
  templates: resolvedTemplates,
@@ -4477,7 +6486,7 @@ function JsonSchemaFormRenderer(props) {
4477
6486
  hasOnBlur: !!onBlur,
4478
6487
  validationDisplay
4479
6488
  });
4480
- const showFieldErrors = useCallback8(
6489
+ const showFieldErrors = useCallback9(
4481
6490
  (fieldName) => fieldErrorsVisible(
4482
6491
  validationDisplay,
4483
6492
  effectiveSubmitAttempted,
@@ -4485,13 +6494,13 @@ function JsonSchemaFormRenderer(props) {
4485
6494
  ),
4486
6495
  [validationDisplay, effectiveSubmitAttempted, touched]
4487
6496
  );
4488
- const fields = useMemo9(
6497
+ const fields = useMemo12(
4489
6498
  () => orderedProperties.flatMap(([fieldName, property]) => {
4490
6499
  const rule = uiSchema[fieldName]?.["x-ui-rule"] ?? property["x-ui-rule"];
4491
6500
  const { visible, enabled } = evaluateUiRule(rule, rootData);
4492
6501
  if (!visible) return [];
4493
6502
  return [
4494
- /* @__PURE__ */ jsx37(
6503
+ /* @__PURE__ */ jsx48(
4495
6504
  FormFieldRenderer,
4496
6505
  {
4497
6506
  fieldName,
@@ -4547,7 +6556,7 @@ function JsonSchemaFormRenderer(props) {
4547
6556
  resolvedTemplates,
4548
6557
  layoutConfig?.variant
4549
6558
  );
4550
- const layoutFields = useMemo9(() => {
6559
+ const layoutFields = useMemo12(() => {
4551
6560
  if (groupDefs && groupDefs.length > 0) {
4552
6561
  return layoutFieldsInGroups(
4553
6562
  fields,
@@ -4572,17 +6581,17 @@ function JsonSchemaFormRenderer(props) {
4572
6581
  rootData,
4573
6582
  groupTemplate
4574
6583
  ]);
4575
- const hiddenErrorCount = useMemo9(() => {
6584
+ const hiddenErrorCount = useMemo12(() => {
4576
6585
  if (!groupDefs?.length || fieldErrors.length === 0) return 0;
4577
6586
  const hidden = hiddenGroupFieldNames(groupDefs, rootData);
4578
6587
  if (hidden.size === 0) return 0;
4579
6588
  return fieldErrors.filter((e) => hidden.has(pointerRoot(e.pointer))).length;
4580
6589
  }, [groupDefs, fieldErrors, rootData]);
4581
- const formLevelErrors = useMemo9(() => {
6590
+ const formLevelErrors = useMemo12(() => {
4582
6591
  if (validationDisplay !== "always" && !submitAttempted) return [];
4583
6592
  return fieldErrors.filter((e) => e.pointer === "").map((e) => e.message);
4584
6593
  }, [fieldErrors, validationDisplay, submitAttempted]);
4585
- return /* @__PURE__ */ jsx37(NestedFormContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx37(
6594
+ return /* @__PURE__ */ jsx48(NestedFormContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx48(
4586
6595
  FormContainer,
4587
6596
  {
4588
6597
  displayContents,
@@ -4609,7 +6618,7 @@ function JsonSchemaFormRenderer(props) {
4609
6618
  }
4610
6619
 
4611
6620
  // src/hooks/useResolvedSchema.ts
4612
- import { useMemo as useMemo10 } from "react";
6621
+ import { useMemo as useMemo13 } from "react";
4613
6622
  import {
4614
6623
  getSchemaProperties as getSchemaProperties5,
4615
6624
  resolveSchema as resolveSchema2
@@ -4619,11 +6628,11 @@ function useResolvedSchema({
4619
6628
  formData,
4620
6629
  uiSchema
4621
6630
  }) {
4622
- const resolvedSchema = useMemo10(
6631
+ const resolvedSchema = useMemo13(
4623
6632
  () => resolveSchema2(schema, formData),
4624
6633
  [schema, formData]
4625
6634
  );
4626
- const orderedProperties = useMemo10(() => {
6635
+ const orderedProperties = useMemo13(() => {
4627
6636
  const properties = getSchemaProperties5(resolvedSchema);
4628
6637
  const rawOrder = uiSchema["x-ui-order"] ?? resolvedSchema["x-ui-order"];
4629
6638
  const orderArray = Array.isArray(rawOrder) ? rawOrder : void 0;
@@ -4632,8 +6641,13 @@ function useResolvedSchema({
4632
6641
  return { resolvedSchema, orderedProperties };
4633
6642
  }
4634
6643
  export {
6644
+ AbsentReasonsWidget,
4635
6645
  ArrayWidget,
6646
+ CheckboxGroupWidget,
4636
6647
  CheckboxWidget,
6648
+ ChoiceWithTextWidget,
6649
+ CodeableConceptWidget,
6650
+ CodedValueWidget,
4637
6651
  DEFAULT_TEMPLATES,
4638
6652
  DEFAULT_WIDGETS,
4639
6653
  DEFAULT_WIDGET_MAP,
@@ -4647,6 +6661,7 @@ export {
4647
6661
  FormFieldRenderer,
4648
6662
  FormFieldWrapper,
4649
6663
  FormInput,
6664
+ FormKeyValueInput,
4650
6665
  FormMultiSelect,
4651
6666
  FormNumberInput,
4652
6667
  FormQuantityInput,
@@ -4656,10 +6671,12 @@ export {
4656
6671
  GroupErrorBadge,
4657
6672
  JsonEditorWidget,
4658
6673
  JsonSchemaFormRenderer,
6674
+ KeyValueWidget,
4659
6675
  NumberWidget,
4660
6676
  ObjectWidget,
4661
6677
  OneOfWidget,
4662
6678
  QuantityWidget,
6679
+ RadioWidget,
4663
6680
  SectionsGroupTemplate,
4664
6681
  SelectWidget,
4665
6682
  StepperGroupTemplate,