@petrarca/sonnet-schema-designer 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3178 @@
1
+ // src/questionnaire/answerTypes.ts
2
+ import {
3
+ valueRefOf as valueRefOf2
4
+ } from "@petrarca/sonnet-core/schema";
5
+
6
+ // src/questionnaire/limits.ts
7
+ var TEXT_LIMITS = ["minLength", "maxLength", "pattern"];
8
+ var NUMBER_LIMITS = ["minimum", "maximum"];
9
+ var COUNT_LIMITS = ["minItems", "maxItems"];
10
+ var TIME_LIMITS = ["formatMinimum", "formatMaximum"];
11
+ var ANSWER_TYPE_LIMITS = {
12
+ shortText: TEXT_LIMITS,
13
+ longText: TEXT_LIMITS,
14
+ integer: NUMBER_LIMITS,
15
+ decimal: NUMBER_LIMITS,
16
+ multipleChoice: COUNT_LIMITS,
17
+ listOfText: COUNT_LIMITS,
18
+ date: TIME_LIMITS,
19
+ time: TIME_LIMITS,
20
+ dateTime: TIME_LIMITS
21
+ };
22
+ var LIMIT_LABELS = {
23
+ minLength: "Minimum length",
24
+ maxLength: "Maximum length",
25
+ pattern: "Pattern",
26
+ minimum: "Minimum",
27
+ maximum: "Maximum",
28
+ minItems: "At least",
29
+ maxItems: "At most",
30
+ formatMinimum: "Earliest",
31
+ formatMaximum: "Latest"
32
+ };
33
+ function limitsFor(type) {
34
+ return ANSWER_TYPE_LIMITS[type] ?? [];
35
+ }
36
+ function limitsOf(prop2, type) {
37
+ const values = prop2;
38
+ return Object.fromEntries(
39
+ limitsFor(type).filter((k) => values[k] !== void 0).map((k) => [k, values[k]])
40
+ );
41
+ }
42
+
43
+ // src/questionnaire/valueTypes.ts
44
+ import {
45
+ bindingOf,
46
+ CODEABLE_CONCEPT_TYPE,
47
+ CODING_TYPE,
48
+ QUANTITY_TYPE,
49
+ unitRangesKeywords,
50
+ unitRangesOf,
51
+ valueRefKeywords,
52
+ valueRefOf
53
+ } from "@petrarca/sonnet-core/schema";
54
+
55
+ // src/questionnaire/optionsListName.ts
56
+ var OPTIONS_LIST_WIDGET = "options-list";
57
+ var SHOW_IF_WIDGET = "show-if";
58
+
59
+ // src/questionnaire/valueTypes.ts
60
+ function readUnits(prop2, ui) {
61
+ const labels = new Map(
62
+ (ui["x-ui-options"]?.units ?? []).map(
63
+ (u) => [u.value, u.label]
64
+ )
65
+ );
66
+ const ranges = unitRangesOf(prop2) ?? {};
67
+ return (prop2.properties?.unit?.enum ?? []).map((v) => ({
68
+ value: String(v),
69
+ label: labels.get(String(v)) ?? String(v),
70
+ ...withoutNone(ranges[String(v)] ?? {})
71
+ }));
72
+ }
73
+ function withoutNone(range) {
74
+ return Object.fromEntries(
75
+ Object.entries(range).filter(([, v]) => v !== void 0)
76
+ );
77
+ }
78
+ function writeUnits(units) {
79
+ const ranges = Object.fromEntries(
80
+ units.map((u) => [u.value, { min: u.min, max: u.max }])
81
+ );
82
+ return {
83
+ prop: {
84
+ properties: units.length ? { unit: { enum: units.map((u) => u.value) } } : void 0,
85
+ ...unitRangesKeywords(ranges)
86
+ },
87
+ options: {
88
+ units: units.length ? units.map((u) => ({ value: u.value, label: u.label || u.value })) : void 0
89
+ }
90
+ };
91
+ }
92
+ var QUANTITY_ANSWER = {
93
+ type: QUANTITY_TYPE,
94
+ keywords: ["properties", "allOf"],
95
+ options: ["units"],
96
+ accepts(prop2) {
97
+ const { unit, ...others } = prop2.properties ?? {};
98
+ const narrowsUnits = !unit || Object.keys(unit).length === 1 && Array.isArray(unit.enum);
99
+ const units = (unit?.enum ?? []).map(String);
100
+ const ranges = unitRangesOf(prop2);
101
+ const rangesOfUnits = !!ranges && Object.keys(ranges).every((u) => units.includes(u));
102
+ return Object.keys(others).length === 0 && narrowsUnits && rangesOfUnits;
103
+ },
104
+ settings: {
105
+ fields: {
106
+ units: {
107
+ type: "array",
108
+ title: "Units",
109
+ description: "The units offered, by their UCUM code (kg, cm, mm[Hg]) and label, and the range of each. None: any unit, typed.",
110
+ "x-ui-widget": OPTIONS_LIST_WIDGET,
111
+ "x-ui-options": { itemLabel: "unit" },
112
+ items: {
113
+ type: "object",
114
+ properties: {
115
+ value: { type: "string", title: "Code" },
116
+ label: { type: "string", title: "Label" },
117
+ min: { type: "number", title: "Min" },
118
+ max: { type: "number", title: "Max" }
119
+ }
120
+ }
121
+ }
122
+ },
123
+ read: (prop2, ui) => ({ units: readUnits(prop2, ui) }),
124
+ write: (values) => writeUnits(
125
+ (values.units ?? []).filter((u) => u.value)
126
+ )
127
+ }
128
+ };
129
+ function listOf(binding) {
130
+ if (binding?.system && !binding.valueSet)
131
+ return { list: binding.system, listKind: "system" };
132
+ return { list: binding?.valueSet ?? "", listKind: "valueSet" };
133
+ }
134
+ function readCoding(prop2, ui) {
135
+ const binding = bindingOf(prop2);
136
+ const mode2 = ui["x-ui-options"]?.mode;
137
+ return {
138
+ ...listOf(binding),
139
+ version: binding?.version ?? "",
140
+ strength: binding?.strength ?? "required",
141
+ several: valueRefOf(prop2)?.multiple ?? false,
142
+ mode: mode2 ?? "auto"
143
+ };
144
+ }
145
+ function codingBindingOf(values) {
146
+ const list2 = String(values.list ?? "").trim();
147
+ if (!list2) return void 0;
148
+ const version2 = String(values.version ?? "").trim();
149
+ return {
150
+ [values.listKind === "system" ? "system" : "valueSet"]: list2,
151
+ ...version2 && { version: version2 },
152
+ strength: values.strength === "extensible" ? "extensible" : "required"
153
+ };
154
+ }
155
+ var CODING_ANSWER = {
156
+ type: CODING_TYPE,
157
+ keywords: ["x-binding"],
158
+ options: ["mode"],
159
+ // Narrowed only by its binding, and that one list.
160
+ accepts(prop2) {
161
+ const binding = prop2["x-binding"];
162
+ return !prop2.properties && !(binding?.valueSet && binding?.system);
163
+ },
164
+ settings: {
165
+ fields: {
166
+ list: {
167
+ type: "string",
168
+ title: "List",
169
+ description: "The codes offered: a list the terminology service knows, by its identifier."
170
+ },
171
+ listKind: {
172
+ type: "string",
173
+ title: "The list is a",
174
+ enum: ["valueSet", "system"],
175
+ "x-ui-widget": "radio",
176
+ "x-ui-options": {
177
+ enumNames: { valueSet: "Value set", system: "Code system" },
178
+ orientation: "horizontal"
179
+ }
180
+ },
181
+ version: {
182
+ type: "string",
183
+ title: "Version",
184
+ description: "None: the list's current version."
185
+ },
186
+ strength: {
187
+ type: "string",
188
+ title: "Answers",
189
+ enum: ["required", "extensible"],
190
+ "x-ui-widget": "radio",
191
+ "x-ui-options": {
192
+ enumNames: {
193
+ required: "Only the list's codes",
194
+ extensible: "The list's codes, or another answer"
195
+ }
196
+ }
197
+ },
198
+ several: { type: "boolean", title: "Several answers" },
199
+ mode: {
200
+ type: "string",
201
+ title: "Shown as",
202
+ enum: ["auto", "list", "search"],
203
+ "x-ui-widget": "radio",
204
+ "x-ui-options": {
205
+ enumNames: {
206
+ auto: "As the list's size decides",
207
+ list: "The whole list",
208
+ search: "A search"
209
+ }
210
+ }
211
+ }
212
+ },
213
+ read: readCoding,
214
+ // An open list takes a text of one's own too: a coding, or a text; several, an array of them.
215
+ write(values) {
216
+ const binding = codingBindingOf(values);
217
+ return {
218
+ prop: {
219
+ ...valueRefKeywords(
220
+ CODING_TYPE.id,
221
+ binding?.strength === "extensible",
222
+ values.several === true
223
+ ),
224
+ "x-binding": binding
225
+ },
226
+ options: {
227
+ mode: values.mode === "list" || values.mode === "search" ? values.mode : void 0
228
+ }
229
+ };
230
+ }
231
+ }
232
+ };
233
+ function readLists(binding) {
234
+ return (binding?.additional ?? []).map((a) => ({
235
+ value: a.valueSet ?? a.system ?? "",
236
+ label: a.title ?? "",
237
+ ...a.required ? { required: true } : {},
238
+ ...a.valueSet ? { kind: "valueSet" } : {},
239
+ ...a.purpose ? { purpose: a.purpose } : {}
240
+ }));
241
+ }
242
+ function writeLists(rows) {
243
+ return (rows ?? []).filter((r) => r.value.trim()).map((r) => ({
244
+ [r.kind === "valueSet" ? "valueSet" : "system"]: r.value.trim(),
245
+ ...r.label ? { title: r.label } : {},
246
+ ...r.required ? { required: true } : {},
247
+ ...r.purpose ? { purpose: r.purpose } : {}
248
+ }));
249
+ }
250
+ var { list, listKind, version, strength, mode } = CODING_ANSWER.settings.fields;
251
+ var CODEABLE_CONCEPT_ANSWER = {
252
+ type: CODEABLE_CONCEPT_TYPE,
253
+ keywords: ["x-binding"],
254
+ options: ["mode", "text"],
255
+ // Narrowed only by its binding, and that one first list.
256
+ accepts(prop2) {
257
+ const binding = prop2["x-binding"];
258
+ return !prop2.properties && !(binding?.valueSet && binding?.system);
259
+ },
260
+ settings: {
261
+ fields: {
262
+ list,
263
+ listKind,
264
+ version,
265
+ strength,
266
+ additional: {
267
+ type: "array",
268
+ title: "Further lists",
269
+ description: "Where the concept's other codes come from -- an identifier the terminology service knows. Required: a value with codes needs one of it.",
270
+ "x-ui-widget": OPTIONS_LIST_WIDGET,
271
+ "x-ui-options": { itemLabel: "list", wide: true },
272
+ items: {
273
+ type: "object",
274
+ properties: {
275
+ value: { type: "string", title: "Identifier" },
276
+ label: { type: "string", title: "Title" },
277
+ required: { type: "boolean", title: "Required" }
278
+ }
279
+ }
280
+ },
281
+ mode,
282
+ // Not `text`: the panel's own field of that name is the question's text.
283
+ offerText: {
284
+ type: "boolean",
285
+ title: "Offer a text",
286
+ description: "An answer in words beside the codes -- or when no code fits."
287
+ }
288
+ },
289
+ read(prop2, ui) {
290
+ const { several: _several, ...first } = readCoding(prop2, ui);
291
+ const options = ui["x-ui-options"];
292
+ return {
293
+ ...first,
294
+ additional: readLists(bindingOf(prop2)),
295
+ offerText: options?.text === true
296
+ };
297
+ },
298
+ write(values) {
299
+ const binding = codingBindingOf(values);
300
+ const additional = writeLists(values.additional);
301
+ return {
302
+ prop: {
303
+ "x-binding": binding && {
304
+ ...binding,
305
+ ...additional.length ? { additional } : {}
306
+ }
307
+ },
308
+ options: {
309
+ mode: values.mode === "list" || values.mode === "search" ? values.mode : void 0,
310
+ text: values.offerText === true ? true : void 0
311
+ }
312
+ };
313
+ }
314
+ }
315
+ };
316
+ var DEFAULT_VALUE_TYPES = [
317
+ QUANTITY_ANSWER,
318
+ CODING_ANSWER,
319
+ CODEABLE_CONCEPT_ANSWER
320
+ ];
321
+ function valueTypeOf(prop2, types) {
322
+ const ref = valueRefOf(prop2)?.ref;
323
+ const found = ref ? types.find((t) => t.type.id === ref) : void 0;
324
+ return found && (found.accepts?.(prop2) ?? true) ? found : void 0;
325
+ }
326
+ function valueTypeAnswer(answerType, types) {
327
+ return types.find((t) => t.type.id === answerType);
328
+ }
329
+
330
+ // src/questionnaire/answerTypes.ts
331
+ var ANSWER_TYPE_LABELS = {
332
+ yesNo: "Yes / no",
333
+ shortText: "Short text",
334
+ longText: "Long text",
335
+ integer: "Integer",
336
+ decimal: "Decimal",
337
+ date: "Date",
338
+ time: "Time",
339
+ dateTime: "Date and time",
340
+ singleChoice: "Single choice",
341
+ multipleChoice: "Multiple choice",
342
+ choiceWithText: "Choice with text",
343
+ listOfText: "List of text",
344
+ parts: "Parts"
345
+ };
346
+ function isBuiltinAnswerType(type) {
347
+ return type in ANSWER_TYPE_LABELS;
348
+ }
349
+ function answerTypeLabel(type, types = DEFAULT_VALUE_TYPES) {
350
+ if (isBuiltinAnswerType(type)) return ANSWER_TYPE_LABELS[type];
351
+ return types.find((t) => t.type.id === type)?.type.label ?? type;
352
+ }
353
+ var CHOICE_WITH_TEXT_WIDGET = "choice-with-text";
354
+ var CONTAINER_TYPES = ["choiceWithText", "parts"];
355
+ function isContainerType(type) {
356
+ return CONTAINER_TYPES.includes(type);
357
+ }
358
+ var CHILD_TYPES = {
359
+ choiceWithText: { choice: ["singleChoice"], text: ["shortText", "longText"] },
360
+ parts: [
361
+ "yesNo",
362
+ "shortText",
363
+ "longText",
364
+ "integer",
365
+ "decimal",
366
+ "date",
367
+ "time",
368
+ "dateTime",
369
+ "singleChoice"
370
+ ]
371
+ };
372
+ var QUESTION_KEYWORDS = ["title", "description", "readOnly"];
373
+ var QUESTION_HINTS = ["x-ui-title", "x-ui-description", "x-ui-placeholder"];
374
+ function pattern(type, keywords, widgets = [void 0], options) {
375
+ return { type, keywords, widgets, options };
376
+ }
377
+ var STRING_FORMATS = {
378
+ date: "date",
379
+ time: "time",
380
+ "date-time": "dateTime"
381
+ };
382
+ var SCALAR_PATTERNS = {
383
+ boolean: pattern("yesNo", ["type"], [void 0, "checkbox"]),
384
+ integer: pattern("integer", ["type"], [void 0, "number"]),
385
+ number: pattern("decimal", ["type"], [void 0, "number"])
386
+ };
387
+ function patternOf(prop2, widget, types) {
388
+ const valueType = valueTypeOrRef(prop2, types);
389
+ if (valueType !== void 0) return valueType;
390
+ if (isSingleChoice(prop2)) {
391
+ return pattern(
392
+ "singleChoice",
393
+ ["type", "enum"],
394
+ [void 0, "select", "radio"],
395
+ ["enumNames"]
396
+ );
397
+ }
398
+ if (prop2.type === "string") return stringPattern(prop2, widget);
399
+ if (prop2.type === "array") return arrayPattern(prop2);
400
+ if (prop2.type === "object") return objectPattern(prop2, widget);
401
+ return (prop2.type && SCALAR_PATTERNS[prop2.type]) ?? null;
402
+ }
403
+ function valueTypeOrRef(prop2, types) {
404
+ const valueType = valueRefOf2(prop2) && valueTypePattern(prop2, types);
405
+ if (valueType) return valueType;
406
+ return prop2.$ref ? null : void 0;
407
+ }
408
+ function isSingleChoice(prop2) {
409
+ const hasEnum = Array.isArray(prop2.enum) && prop2.enum.length > 0;
410
+ return hasEnum && (prop2.type === "string" || prop2.type === void 0);
411
+ }
412
+ function valueTypePattern(prop2, types) {
413
+ const found = valueTypeOf(prop2, types);
414
+ if (!found) return null;
415
+ const widget = found.type.definition["x-ui-widget"];
416
+ return pattern(
417
+ found.type.id,
418
+ ["$ref", "anyOf", "type", "items", ...found.keywords ?? []],
419
+ [void 0, ...typeof widget === "string" ? [widget] : []],
420
+ found.options
421
+ );
422
+ }
423
+ function stringPattern(prop2, widget) {
424
+ const byFormat = prop2.format ? STRING_FORMATS[prop2.format] : void 0;
425
+ if (byFormat) {
426
+ const widgetName = byFormat === "dateTime" ? "date-time" : byFormat;
427
+ return pattern(byFormat, ["type", "format"], [void 0, widgetName]);
428
+ }
429
+ if (widget === "textarea") return pattern("longText", ["type"], ["textarea"]);
430
+ return pattern("shortText", ["type"], [void 0, "text"]);
431
+ }
432
+ function arrayPattern(prop2) {
433
+ const items = prop2.items;
434
+ if (!items || items.type !== "string" || hasOtherKeys(items, ["type", "enum"])) {
435
+ return null;
436
+ }
437
+ return Array.isArray(items.enum) && items.enum.length > 0 ? pattern(
438
+ "multipleChoice",
439
+ ["type", "items", "uniqueItems"],
440
+ [void 0, "select", "checkbox-group"],
441
+ ["enumNames"]
442
+ ) : pattern("listOfText", ["type", "items"], [void 0, "tags"]);
443
+ }
444
+ function objectPattern(prop2, widget) {
445
+ if (widget === CHOICE_WITH_TEXT_WIDGET && isChoiceWithText(prop2)) {
446
+ return pattern(
447
+ "choiceWithText",
448
+ ["type", "properties", "required", "allOf"],
449
+ [CHOICE_WITH_TEXT_WIDGET]
450
+ );
451
+ }
452
+ if (isParts(prop2)) {
453
+ return pattern("parts", [
454
+ "type",
455
+ "properties",
456
+ "required",
457
+ "x-ui-order",
458
+ "x-ui-layout"
459
+ ]);
460
+ }
461
+ return null;
462
+ }
463
+ function isChoiceWithText(prop2) {
464
+ const children = choiceWithTextChildren(prop2);
465
+ if (!children) return false;
466
+ const { choice, text } = children;
467
+ return Array.isArray(choice.enum) && choice.enum.length > 0 && text.type === "string" && !text.enum;
468
+ }
469
+ function choiceTextWhen(prop2) {
470
+ const member = prop2.allOf?.length === 1 ? prop2.allOf[0] : void 0;
471
+ const constraint = member?.if?.properties?.choice;
472
+ return constraint && "const" in constraint ? String(constraint.const) : void 0;
473
+ }
474
+ function otherTextMember(when, text) {
475
+ return {
476
+ if: { properties: { choice: { const: when } }, required: ["choice"] },
477
+ then: { properties: { text }, required: ["text"] }
478
+ };
479
+ }
480
+ function choiceWithTextChildren(prop2) {
481
+ const p = prop2.properties ?? {};
482
+ const keys = Object.keys(p).sort().join(",");
483
+ if (keys === "choice,text" && !prop2.allOf)
484
+ return { choice: p.choice, text: p.text };
485
+ return keys === "choice" ? otherVariantChildren(prop2) : null;
486
+ }
487
+ function otherVariantChildren(prop2) {
488
+ const { choice, text } = rawChoiceAndText(prop2);
489
+ const when = choiceTextWhen(prop2);
490
+ if (!choice || !text || when === void 0 || prop2.allOf?.length !== 1)
491
+ return null;
492
+ const isOption = (choice.enum ?? []).map(String).includes(when);
493
+ const exact = JSON.stringify(prop2.allOf[0]) === JSON.stringify(otherTextMember(when, text));
494
+ return isOption && exact ? { choice, text } : null;
495
+ }
496
+ function rawChoiceAndText(prop2) {
497
+ const conditional = prop2.allOf?.[0]?.then?.properties?.text;
498
+ return {
499
+ choice: prop2.properties?.choice,
500
+ text: prop2.properties?.text ?? conditional
501
+ };
502
+ }
503
+ function isParts(prop2) {
504
+ const p = prop2.properties;
505
+ const rows = prop2["x-ui-layout"]?.rows;
506
+ if (!p || !rows || rows.length !== 1) return false;
507
+ const keys = Object.keys(p);
508
+ const inRow = new Set(rows[0].fields);
509
+ return keys.length > 0 && keys.every((k) => inRow.has(k)) && keys.every((k) => p[k].type !== "object" && p[k].type !== "array");
510
+ }
511
+ function hasOtherKeys(obj, allowed) {
512
+ return Object.keys(obj).some((k) => !allowed.includes(k));
513
+ }
514
+ function hintExtras(ui, p) {
515
+ const extras = [];
516
+ const container = CONTAINER_TYPES.includes(p.type);
517
+ for (const key of Object.keys(ui)) {
518
+ if (QUESTION_HINTS.includes(key) || key === "x-ui-widget") continue;
519
+ if (container && key === "x-ui-fields") continue;
520
+ if (key === "x-ui-options") {
521
+ const options = Object.keys(ui[key] ?? {});
522
+ extras.push(
523
+ ...options.filter((o) => !p.options?.includes(o)).map((o) => `x-ui-options.${o}`)
524
+ );
525
+ continue;
526
+ }
527
+ extras.push(key);
528
+ }
529
+ return extras;
530
+ }
531
+ function recognizeAnswerType(prop2, ui, types = DEFAULT_VALUE_TYPES) {
532
+ const widget = ui["x-ui-widget"];
533
+ const p = patternOf(prop2, widget, types);
534
+ if (!p) return null;
535
+ const known = /* @__PURE__ */ new Set([
536
+ ...QUESTION_KEYWORDS,
537
+ ...p.keywords,
538
+ ...limitsFor(p.type),
539
+ ...isContainerType(p.type) ? [] : ["default"]
540
+ ]);
541
+ const schemaExtras = Object.keys(prop2).filter(
542
+ (k) => !known.has(k) && !k.startsWith("x-ui-")
543
+ );
544
+ const widgetExtra = p.widgets.includes(widget) ? [] : ["x-ui-widget"];
545
+ const extras = [...schemaExtras, ...widgetExtra, ...hintExtras(ui, p)];
546
+ if (!CONTAINER_TYPES.includes(p.type)) return { type: p.type, extras };
547
+ const children = childExtras(prop2, ui, p.type);
548
+ return children ? { type: p.type, extras: [...extras, ...children] } : null;
549
+ }
550
+ function childExtras(prop2, ui, type) {
551
+ const fields = ui["x-ui-fields"] ?? {};
552
+ const allowed = CHILD_TYPES[type];
553
+ const extras = [];
554
+ for (const [key, child] of Object.entries(containerChildren(prop2, type))) {
555
+ const childUi = { ...embeddedHints(child), ...fields[key] };
556
+ const answer = recognizeAnswerType(child, childUi);
557
+ const types = Array.isArray(allowed) ? allowed : allowed?.[key];
558
+ if (!answer || !types?.includes(answer.type)) return null;
559
+ extras.push(...answer.extras.map((e) => `${key}.${e}`));
560
+ }
561
+ return extras;
562
+ }
563
+ function containerChildren(prop2, type) {
564
+ if (type !== "choiceWithText") return prop2.properties ?? {};
565
+ const children = choiceWithTextChildren(prop2);
566
+ return children ? { choice: children.choice, text: children.text } : {};
567
+ }
568
+ function embeddedHints(prop2) {
569
+ const hints = {};
570
+ for (const [key, value] of Object.entries(prop2)) {
571
+ if (key.startsWith("x-ui-") && key !== "x-ui-layout" && key !== "x-ui-order") {
572
+ hints[key] = value;
573
+ }
574
+ }
575
+ return hints;
576
+ }
577
+ var NEW_OPTIONS = ["option_1", "option_2"];
578
+ var FRAGMENTS = {
579
+ yesNo: () => ({ prop: { type: "boolean" }, ui: {} }),
580
+ shortText: () => ({ prop: { type: "string" }, ui: {} }),
581
+ longText: () => ({
582
+ prop: { type: "string" },
583
+ ui: { "x-ui-widget": "textarea" }
584
+ }),
585
+ integer: () => ({ prop: { type: "integer" }, ui: {} }),
586
+ decimal: () => ({ prop: { type: "number" }, ui: {} }),
587
+ date: () => ({ prop: { type: "string", format: "date" }, ui: {} }),
588
+ time: () => ({ prop: { type: "string", format: "time" }, ui: {} }),
589
+ dateTime: () => ({ prop: { type: "string", format: "date-time" }, ui: {} }),
590
+ singleChoice: (options) => ({
591
+ prop: { type: "string", enum: [...options] },
592
+ ui: {}
593
+ }),
594
+ multipleChoice: (options) => ({
595
+ prop: {
596
+ type: "array",
597
+ items: { type: "string", enum: [...options] },
598
+ uniqueItems: true
599
+ },
600
+ ui: {}
601
+ }),
602
+ listOfText: () => ({
603
+ prop: { type: "array", items: { type: "string" } },
604
+ ui: {}
605
+ }),
606
+ choiceWithText: (options) => ({
607
+ prop: {
608
+ type: "object",
609
+ properties: {
610
+ choice: { type: "string", enum: [...options] },
611
+ text: { type: "string" }
612
+ }
613
+ },
614
+ ui: { "x-ui-widget": CHOICE_WITH_TEXT_WIDGET }
615
+ }),
616
+ parts: () => ({
617
+ prop: {
618
+ type: "object",
619
+ properties: { part_1: { type: "string" }, part_2: { type: "string" } },
620
+ "x-ui-layout": { rows: [{ fields: ["part_1", "part_2"] }] }
621
+ },
622
+ ui: {}
623
+ })
624
+ };
625
+ function answerTypeFragment(type, options = NEW_OPTIONS) {
626
+ if (!isBuiltinAnswerType(type)) return { prop: { $ref: type }, ui: {} };
627
+ return FRAGMENTS[type](options);
628
+ }
629
+
630
+ // src/questionnaire/classify.ts
631
+ import { isOneOf as isOneOf3 } from "@petrarca/sonnet-core/schema";
632
+
633
+ // src/model/paths.ts
634
+ import {
635
+ isOneOf,
636
+ getDiscriminator,
637
+ getBranchLabel
638
+ } from "@petrarca/sonnet-core/schema";
639
+ function isReservedKey(key) {
640
+ return /^#(branch|condition|def|slot):/.test(key);
641
+ }
642
+ function prop(key) {
643
+ return { kind: "prop", key };
644
+ }
645
+ function branch(index) {
646
+ return { kind: "branch", index };
647
+ }
648
+ function condition(index, combinator = "allOf") {
649
+ return { kind: "condition", combinator, index };
650
+ }
651
+ function slot(name) {
652
+ return { kind: "slot", slot: name };
653
+ }
654
+ function def(name) {
655
+ return { kind: "def", name };
656
+ }
657
+ var SEP = "/";
658
+ function escapeToken(raw) {
659
+ return raw.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
660
+ }
661
+ function unescapeToken(raw) {
662
+ return raw.replace(/\\\//g, "/").replace(/\\\\/g, "\\");
663
+ }
664
+ function encodeSegment(seg) {
665
+ switch (seg.kind) {
666
+ case "prop":
667
+ return escapeToken(seg.key);
668
+ case "branch":
669
+ return `#branch:${seg.index}`;
670
+ case "condition":
671
+ return `#condition:${seg.combinator}:${seg.index}`;
672
+ case "slot":
673
+ return `#slot:${seg.slot}`;
674
+ case "def":
675
+ return `#def:${escapeToken(seg.name)}`;
676
+ }
677
+ }
678
+ var VALID_SLOTS = ["then", "else", "items"];
679
+ function safeIndex(raw) {
680
+ const n = Number(raw);
681
+ return Number.isInteger(n) && n >= 0 ? n : 0;
682
+ }
683
+ function decodeCondition(rest) {
684
+ const sep = rest.indexOf(":");
685
+ const combinatorRaw = sep >= 0 ? rest.slice(0, sep) : "allOf";
686
+ const combinator = combinatorRaw === "anyOf" ? "anyOf" : "allOf";
687
+ const index = safeIndex(sep >= 0 ? rest.slice(sep + 1) : rest);
688
+ return { kind: "condition", combinator, index };
689
+ }
690
+ function decodeSegment(token) {
691
+ if (token.startsWith("#branch:")) {
692
+ return { kind: "branch", index: safeIndex(token.slice("#branch:".length)) };
693
+ }
694
+ if (token.startsWith("#condition:")) {
695
+ return decodeCondition(token.slice("#condition:".length));
696
+ }
697
+ if (token.startsWith("#slot:")) {
698
+ const s = token.slice("#slot:".length);
699
+ return { kind: "slot", slot: VALID_SLOTS.includes(s) ? s : "items" };
700
+ }
701
+ if (token.startsWith("#def:")) {
702
+ return { kind: "def", name: unescapeToken(token.slice("#def:".length)) };
703
+ }
704
+ return { kind: "prop", key: unescapeToken(token) };
705
+ }
706
+ function splitTokens(key) {
707
+ const tokens = [];
708
+ let current = "";
709
+ for (let i = 0; i < key.length; i++) {
710
+ const ch = key[i];
711
+ if (ch === "\\" && i + 1 < key.length) {
712
+ current += ch + key[i + 1];
713
+ i++;
714
+ } else if (ch === SEP) {
715
+ tokens.push(current);
716
+ current = "";
717
+ } else {
718
+ current += ch;
719
+ }
720
+ }
721
+ tokens.push(current);
722
+ return tokens;
723
+ }
724
+ function pathKey(path) {
725
+ return path.map(encodeSegment).join(SEP);
726
+ }
727
+ function parsePathKey(key) {
728
+ if (key === "") return [];
729
+ return splitTokens(key).map(decodeSegment);
730
+ }
731
+ function segmentIdentity(seg) {
732
+ switch (seg.kind) {
733
+ case "prop":
734
+ return `prop:${seg.key}`;
735
+ case "branch":
736
+ return `branch:${seg.index}`;
737
+ case "condition":
738
+ return `condition:${seg.combinator}:${seg.index}`;
739
+ case "slot":
740
+ return `slot:${seg.slot}`;
741
+ case "def":
742
+ return `def:${seg.name}`;
743
+ }
744
+ }
745
+ function sameSegment(a, b) {
746
+ return segmentIdentity(a) === segmentIdentity(b);
747
+ }
748
+ function samePath(a, b) {
749
+ if (a === null || b === null) return a === b;
750
+ return a.length === b.length && a.every((seg, i) => sameSegment(seg, b[i]));
751
+ }
752
+ function childContainer(prop2) {
753
+ const typeIsObjectLike = prop2.type === "object" || prop2.type === void 0;
754
+ if (typeIsObjectLike && prop2.properties && !prop2.oneOf)
755
+ return prop2.properties;
756
+ if (prop2.type === "array" && prop2.items?.type === "object") {
757
+ return prop2.items.properties;
758
+ }
759
+ return void 0;
760
+ }
761
+ function isConditionalMember(member) {
762
+ return !!member.if;
763
+ }
764
+ function membersOfCombinator(property, combinator) {
765
+ const arr = property[combinator];
766
+ if (!Array.isArray(arr)) return [];
767
+ return arr.map((member, index) => ({ member, combinator, index })).filter(({ member }) => isConditionalMember(member));
768
+ }
769
+ function conditionalMembers(property) {
770
+ return [
771
+ ...membersOfCombinator(property, "allOf"),
772
+ ...membersOfCombinator(property, "anyOf")
773
+ ];
774
+ }
775
+ function conditionLabel(member) {
776
+ const field = Object.keys(member.if?.properties ?? {})[0] ?? member.if?.required?.[0];
777
+ return field ? `if ${field}` : "if ...";
778
+ }
779
+ function conditionalSlots(member) {
780
+ const slots = [];
781
+ const add = (name, sub) => {
782
+ if (sub) {
783
+ slots.push({
784
+ segment: slot(name),
785
+ label: name,
786
+ property: sub,
787
+ kind: "conditional",
788
+ removable: false,
789
+ reorderable: false
790
+ });
791
+ }
792
+ };
793
+ add("then", member.then);
794
+ add("else", member.else);
795
+ return slots;
796
+ }
797
+ function definitionsOf(property) {
798
+ return property.$defs ?? property.definitions;
799
+ }
800
+ function definitionSlots(property) {
801
+ const defs = definitionsOf(property);
802
+ if (!defs) return [];
803
+ return Object.keys(defs).map((name) => ({
804
+ segment: def(name),
805
+ label: name,
806
+ property: defs[name],
807
+ kind: "definition",
808
+ removable: true,
809
+ reorderable: false
810
+ }));
811
+ }
812
+ function childSlots(property) {
813
+ if (isConditionalMember(property)) return conditionalSlots(property);
814
+ if (isOneOf(property)) {
815
+ const discriminator = getDiscriminator(property);
816
+ return (property.oneOf ?? []).map((b, index) => ({
817
+ segment: branch(index),
818
+ label: getBranchLabel(b, discriminator, index),
819
+ property: b,
820
+ kind: "branch",
821
+ removable: true,
822
+ reorderable: true
823
+ }));
824
+ }
825
+ const container = childContainer(property);
826
+ const propertySlots = container ? Object.keys(container).map((key) => ({
827
+ segment: prop(key),
828
+ label: key,
829
+ property: container[key],
830
+ kind: "property",
831
+ removable: true,
832
+ reorderable: true
833
+ })) : [];
834
+ const conditionSlots = conditionalMembers(property).map(
835
+ ({ member, combinator, index }) => ({
836
+ segment: condition(index, combinator),
837
+ label: conditionLabel(member),
838
+ property: member,
839
+ kind: "conditional",
840
+ removable: true,
841
+ reorderable: false
842
+ })
843
+ );
844
+ return [...propertySlots, ...conditionSlots, ...definitionSlots(property)];
845
+ }
846
+ function stepInto(parent, seg) {
847
+ if (seg.kind === "prop") return childContainer(parent)?.[seg.key];
848
+ if (seg.kind === "branch") return parent.oneOf?.[seg.index];
849
+ if (seg.kind === "condition") return parent[seg.combinator]?.[seg.index];
850
+ if (seg.kind === "def") return definitionsOf(parent)?.[seg.name];
851
+ if (seg.slot === "items") return parent.items;
852
+ return parent[seg.slot];
853
+ }
854
+ function schemaAsRootProp(schema) {
855
+ return {
856
+ type: "object",
857
+ properties: schema.properties,
858
+ required: schema.required,
859
+ "x-ui-order": schema["x-ui-order"],
860
+ allOf: schema.allOf,
861
+ anyOf: schema.anyOf,
862
+ $defs: schema.$defs,
863
+ definitions: schema.definitions
864
+ };
865
+ }
866
+ function getProperty(schema, path) {
867
+ if (path.length === 0) return void 0;
868
+ const root = schemaAsRootProp(schema);
869
+ let current = root;
870
+ for (const seg of path) {
871
+ if (!current) return void 0;
872
+ current = stepInto(current, seg);
873
+ }
874
+ return current;
875
+ }
876
+ function isContainer(prop2) {
877
+ return childSlots(prop2).length > 0 || childContainer(prop2) !== void 0;
878
+ }
879
+
880
+ // src/model/mutate.ts
881
+ function segKey(seg) {
882
+ return seg && seg.kind === "prop" ? seg.key : void 0;
883
+ }
884
+ function propKeys(path) {
885
+ return path.flatMap((s) => s.kind === "prop" ? [s.key] : []);
886
+ }
887
+ function clone(v) {
888
+ return structuredClone(v);
889
+ }
890
+ function newProperty(kind) {
891
+ switch (kind) {
892
+ case "object":
893
+ return { type: "object", properties: {} };
894
+ case "array":
895
+ return { type: "array", items: { type: "object", properties: {} } };
896
+ case "oneOf":
897
+ return {
898
+ type: "object",
899
+ discriminator: { propertyName: "kind" },
900
+ oneOf: [newBranch("option_1"), newBranch("option_2")]
901
+ };
902
+ default:
903
+ return { type: "string" };
904
+ }
905
+ }
906
+ function newBranch(value) {
907
+ return {
908
+ title: value,
909
+ properties: { kind: { const: value } }
910
+ };
911
+ }
912
+ function resolveContainer(schema, parentPath) {
913
+ if (parentPath.length === 0) {
914
+ return {
915
+ properties: schema.properties,
916
+ getOrder: () => schema["x-ui-order"],
917
+ setOrder: (o) => {
918
+ if (o) schema["x-ui-order"] = o;
919
+ else delete schema["x-ui-order"];
920
+ },
921
+ getRequired: () => schema.required,
922
+ setRequired: (r) => {
923
+ if (r && r.length) schema.required = r;
924
+ else delete schema.required;
925
+ }
926
+ };
927
+ }
928
+ const owner = getProperty(schema, parentPath);
929
+ if (!owner) return null;
930
+ if (owner.oneOf) return null;
931
+ const target = owner.type === "array" ? owner.items : owner;
932
+ if (!target) return null;
933
+ if (!target.properties) target.properties = {};
934
+ const properties = target.properties;
935
+ return {
936
+ properties,
937
+ getOrder: () => target["x-ui-order"],
938
+ setOrder: (o) => {
939
+ if (o) target["x-ui-order"] = o;
940
+ else delete target["x-ui-order"];
941
+ },
942
+ getRequired: () => target.required,
943
+ setRequired: (r) => {
944
+ if (r && r.length) target.required = r;
945
+ else delete target.required;
946
+ }
947
+ };
948
+ }
949
+ function uniqueKey(base, properties) {
950
+ if (!(base in properties)) return base;
951
+ let i = 2;
952
+ while (`${base}_${i}` in properties) i++;
953
+ return `${base}_${i}`;
954
+ }
955
+ function addProperty(model, parentPath, kind) {
956
+ const schema = clone(model.schema);
957
+ const container = resolveContainer(schema, parentPath);
958
+ if (!container) return { model, path: parentPath };
959
+ const key = uniqueKey("property", container.properties);
960
+ container.properties[key] = newProperty(kind);
961
+ const order = container.getOrder();
962
+ if (order) container.setOrder([...order, key]);
963
+ return {
964
+ model: { schema, uiSchema: model.uiSchema },
965
+ path: [...parentPath, prop(key)]
966
+ };
967
+ }
968
+ function uniqueBranchValue(oneOf) {
969
+ const existing = new Set(oneOf.map((b) => b.title).filter(Boolean));
970
+ let i = 1;
971
+ while (existing.has(`option_${i}`)) i++;
972
+ return `option_${i}`;
973
+ }
974
+ function addBranch(model, path) {
975
+ const schema = clone(model.schema);
976
+ const owner = getProperty(schema, path);
977
+ if (!owner || !owner.oneOf) return { model, path };
978
+ const index = owner.oneOf.length;
979
+ owner.oneOf.push(newBranch(uniqueBranchValue(owner.oneOf)));
980
+ return {
981
+ model: { schema, uiSchema: model.uiSchema },
982
+ path: [...path, branch(index)]
983
+ };
984
+ }
985
+ function removeBranch(model, path) {
986
+ const last = path[path.length - 1];
987
+ if (!last || last.kind !== "branch") return model;
988
+ const schema = clone(model.schema);
989
+ const owner = getProperty(schema, path.slice(0, -1));
990
+ if (!owner?.oneOf) return model;
991
+ owner.oneOf.splice(last.index, 1);
992
+ return { schema, uiSchema: model.uiSchema };
993
+ }
994
+ function moveBranch(model, path, direction) {
995
+ const last = path[path.length - 1];
996
+ if (!last || last.kind !== "branch") return model;
997
+ const schema = clone(model.schema);
998
+ const owner = getProperty(schema, path.slice(0, -1));
999
+ if (!owner?.oneOf) return model;
1000
+ const from = last.index;
1001
+ const to = from + direction;
1002
+ if (to < 0 || to >= owner.oneOf.length) return model;
1003
+ const [moved] = owner.oneOf.splice(from, 1);
1004
+ owner.oneOf.splice(to, 0, moved);
1005
+ return { schema, uiSchema: model.uiSchema };
1006
+ }
1007
+ function newCondition() {
1008
+ return { if: { properties: {} }, then: { properties: {} } };
1009
+ }
1010
+ function addConditional(model, objectPath, combinator = "allOf") {
1011
+ const schema = clone(model.schema);
1012
+ const owner = conditionalOwner(schema, objectPath);
1013
+ if (!owner) return { model, path: objectPath };
1014
+ if (!Array.isArray(owner[combinator])) owner[combinator] = [];
1015
+ const arr = owner[combinator];
1016
+ const index = arr.length;
1017
+ arr.push(newCondition());
1018
+ return {
1019
+ model: { schema, uiSchema: model.uiSchema },
1020
+ path: [...objectPath, condition(index, combinator)]
1021
+ };
1022
+ }
1023
+ function conditionalOwner(schema, objectPath) {
1024
+ if (objectPath.length === 0) return schema;
1025
+ const owner = getProperty(schema, objectPath);
1026
+ if (!owner) return null;
1027
+ const objectLike = owner.type === "object" || owner.type === void 0;
1028
+ if (!objectLike || owner.oneOf) return null;
1029
+ return owner;
1030
+ }
1031
+ function removeConditional(model, path) {
1032
+ const last = path[path.length - 1];
1033
+ if (!last || last.kind !== "condition") return model;
1034
+ const schema = clone(model.schema);
1035
+ const owner = conditionalOwner(schema, path.slice(0, -1));
1036
+ const arr = owner?.[last.combinator];
1037
+ if (!arr) return model;
1038
+ arr.splice(last.index, 1);
1039
+ if (arr.length === 0) delete owner[last.combinator];
1040
+ return { schema, uiSchema: model.uiSchema };
1041
+ }
1042
+ function setCondition(model, path, ifSchema) {
1043
+ const last = path[path.length - 1];
1044
+ if (!last || last.kind !== "condition") return model;
1045
+ const schema = clone(model.schema);
1046
+ const member = getProperty(schema, path);
1047
+ if (!member) return model;
1048
+ member.if = ifSchema;
1049
+ if (!member.then) member.then = { properties: {} };
1050
+ return { schema, uiSchema: model.uiSchema };
1051
+ }
1052
+ function addElse(model, path) {
1053
+ const last = path[path.length - 1];
1054
+ if (!last || last.kind !== "condition") return model;
1055
+ const schema = clone(model.schema);
1056
+ const member = getProperty(schema, path);
1057
+ if (!member || member.else) return model;
1058
+ member.else = { properties: {} };
1059
+ return { schema, uiSchema: model.uiSchema };
1060
+ }
1061
+ function removeElse(model, path) {
1062
+ const last = path[path.length - 1];
1063
+ if (!last || last.kind !== "condition") return model;
1064
+ const schema = clone(model.schema);
1065
+ const member = getProperty(schema, path);
1066
+ if (!member || !member.else) return model;
1067
+ delete member.else;
1068
+ return { schema, uiSchema: model.uiSchema };
1069
+ }
1070
+ function otherCombinator(c) {
1071
+ return c === "allOf" ? "anyOf" : "allOf";
1072
+ }
1073
+ function setConditionalBehavior(model, objectPath, next) {
1074
+ const schema = clone(model.schema);
1075
+ const owner = conditionalOwner(schema, objectPath);
1076
+ if (!owner) return model;
1077
+ const from = otherCombinator(next);
1078
+ const source = owner[from];
1079
+ if (!Array.isArray(source)) return model;
1080
+ const conditionals = source.filter((m) => !!m.if);
1081
+ if (conditionals.length === 0) return model;
1082
+ const rest = source.filter((m) => !m.if);
1083
+ if (rest.length > 0) owner[from] = rest;
1084
+ else delete owner[from];
1085
+ owner[next] = [...owner[next] ?? [], ...conditionals];
1086
+ return { schema, uiSchema: model.uiSchema };
1087
+ }
1088
+ function deleteProperty(model, path) {
1089
+ if (path.length === 0) return model;
1090
+ const schema = clone(model.schema);
1091
+ const parentPath = path.slice(0, -1);
1092
+ const key = segKey(path[path.length - 1]);
1093
+ if (key === void 0) return model;
1094
+ const container = resolveContainer(schema, parentPath);
1095
+ if (!container || !(key in container.properties)) return model;
1096
+ delete container.properties[key];
1097
+ const order = container.getOrder();
1098
+ if (order) container.setOrder(order.filter((k) => k !== key));
1099
+ const required = container.getRequired();
1100
+ if (required) container.setRequired(required.filter((k) => k !== key));
1101
+ const uiSchema = getUiEntryAtPath(model.uiSchema, path) ? setEntryIn(model.uiSchema, propKeys(path), void 0) : model.uiSchema;
1102
+ return { schema, uiSchema };
1103
+ }
1104
+ function renameKeyInPlace(properties, prevKey, nextKey) {
1105
+ const rebuilt = {};
1106
+ for (const [k, v] of Object.entries(properties)) {
1107
+ rebuilt[k === prevKey ? nextKey : k] = v;
1108
+ }
1109
+ for (const k of Object.keys(properties)) delete properties[k];
1110
+ Object.assign(properties, rebuilt);
1111
+ }
1112
+ function renameUiEntry(uiSchema, path, nextKey) {
1113
+ const entry = getUiEntryAtPath(uiSchema, path);
1114
+ if (!entry) return uiSchema;
1115
+ const keys = propKeys(path);
1116
+ const cleared = setEntryIn(uiSchema, keys, void 0);
1117
+ return setEntryIn(cleared, [...keys.slice(0, -1), nextKey], entry);
1118
+ }
1119
+ function updateContainerRefs(container, prevKey, nextKey) {
1120
+ const rename = (k) => k === prevKey ? nextKey : k;
1121
+ const order = container.getOrder();
1122
+ if (order) container.setOrder(order.map(rename));
1123
+ const required = container.getRequired();
1124
+ if (required) container.setRequired(required.map(rename));
1125
+ }
1126
+ function renameProperty(model, path, nextKey) {
1127
+ if (path.length === 0) return { model, path };
1128
+ const parentPath = path.slice(0, -1);
1129
+ const prevKey = segKey(path[path.length - 1]);
1130
+ if (prevKey === void 0) return { model, path };
1131
+ if (nextKey === prevKey || !nextKey || isReservedKey(nextKey))
1132
+ return { model, path };
1133
+ const schema = clone(model.schema);
1134
+ const container = resolveContainer(schema, parentPath);
1135
+ if (!container || !(prevKey in container.properties)) return { model, path };
1136
+ if (nextKey in container.properties) return { model, path };
1137
+ renameKeyInPlace(container.properties, prevKey, nextKey);
1138
+ updateContainerRefs(container, prevKey, nextKey);
1139
+ const uiSchema = renameUiEntry(model.uiSchema, path, nextKey);
1140
+ return {
1141
+ model: { schema, uiSchema },
1142
+ path: [...parentPath, prop(nextKey)]
1143
+ };
1144
+ }
1145
+ function moveProperty(model, path, direction) {
1146
+ if (path.length === 0) return model;
1147
+ const schema = clone(model.schema);
1148
+ const parentPath = path.slice(0, -1);
1149
+ const key = segKey(path[path.length - 1]);
1150
+ if (key === void 0) return model;
1151
+ const container = resolveContainer(schema, parentPath);
1152
+ if (!container) return model;
1153
+ const keys = Object.keys(container.properties);
1154
+ const order = container.getOrder() ?? keys;
1155
+ const full = [...order.filter((k) => keys.includes(k))];
1156
+ for (const k of keys) if (!full.includes(k)) full.push(k);
1157
+ const idx = full.indexOf(key);
1158
+ const swap = idx + direction;
1159
+ if (idx < 0 || swap < 0 || swap >= full.length) return model;
1160
+ [full[idx], full[swap]] = [full[swap], full[idx]];
1161
+ container.setOrder(full);
1162
+ return { schema, uiSchema: model.uiSchema };
1163
+ }
1164
+ function setRequired(model, path, required) {
1165
+ if (path.length === 0) return model;
1166
+ const schema = clone(model.schema);
1167
+ const parentPath = path.slice(0, -1);
1168
+ const key = segKey(path[path.length - 1]);
1169
+ if (key === void 0) return model;
1170
+ const container = resolveContainer(schema, parentPath);
1171
+ if (!container) return model;
1172
+ const current = new Set(container.getRequired() ?? []);
1173
+ if (required) current.add(key);
1174
+ else current.delete(key);
1175
+ container.setRequired([...current]);
1176
+ return { schema, uiSchema: model.uiSchema };
1177
+ }
1178
+ function updateProperty(model, path, patch) {
1179
+ if (path.length === 0) return model;
1180
+ const schema = clone(model.schema);
1181
+ const prop2 = getProperty(schema, path);
1182
+ if (!prop2) return model;
1183
+ for (const [k, v] of Object.entries(patch)) {
1184
+ if (v === void 0) delete prop2[k];
1185
+ else prop2[k] = v;
1186
+ }
1187
+ return { schema, uiSchema: model.uiSchema };
1188
+ }
1189
+ function currentTypeChoice(prop2) {
1190
+ return prop2.oneOf ? "oneOf" : prop2.type ?? "string";
1191
+ }
1192
+ function changePropertyType(model, path, next) {
1193
+ if (path.length === 0 || !next) return model;
1194
+ const schema = clone(model.schema);
1195
+ const prop2 = getProperty(schema, path);
1196
+ if (!prop2 || !prop2.$ref && currentTypeChoice(prop2) === next) return model;
1197
+ delete prop2.properties;
1198
+ delete prop2.items;
1199
+ delete prop2.enum;
1200
+ delete prop2.oneOf;
1201
+ delete prop2.discriminator;
1202
+ delete prop2.const;
1203
+ delete prop2.$ref;
1204
+ if (next !== "object") {
1205
+ delete prop2.allOf;
1206
+ delete prop2.anyOf;
1207
+ }
1208
+ delete prop2.minLength;
1209
+ delete prop2.maxLength;
1210
+ delete prop2.pattern;
1211
+ delete prop2.format;
1212
+ delete prop2.minimum;
1213
+ delete prop2.maximum;
1214
+ delete prop2.minItems;
1215
+ delete prop2.maxItems;
1216
+ if (next === "oneOf") {
1217
+ const seed = newProperty("oneOf");
1218
+ prop2.type = seed.type;
1219
+ prop2.discriminator = seed.discriminator;
1220
+ prop2.oneOf = seed.oneOf;
1221
+ } else {
1222
+ prop2.type = next;
1223
+ if (next === "object") prop2.properties = {};
1224
+ if (next === "array") prop2.items = { type: "string" };
1225
+ }
1226
+ return { schema, uiSchema: model.uiSchema };
1227
+ }
1228
+ function defPointer(name) {
1229
+ return `#/$defs/${name}`;
1230
+ }
1231
+ function setReference(model, path, name) {
1232
+ if (path.length === 0 || !name) return model;
1233
+ const schema = clone(model.schema);
1234
+ const prop2 = getProperty(schema, path);
1235
+ if (!prop2) return model;
1236
+ const defs = schema.$defs ?? schema.definitions;
1237
+ if (!defs || !(name in defs)) return model;
1238
+ if (prop2.type === "array") {
1239
+ prop2.items = { $ref: defPointer(name) };
1240
+ return { schema, uiSchema: model.uiSchema };
1241
+ }
1242
+ makeReference(prop2, name);
1243
+ return { schema, uiSchema: model.uiSchema };
1244
+ }
1245
+ function makeReference(prop2, name) {
1246
+ delete prop2.properties;
1247
+ delete prop2.items;
1248
+ delete prop2.enum;
1249
+ delete prop2.oneOf;
1250
+ delete prop2.discriminator;
1251
+ delete prop2.const;
1252
+ delete prop2.allOf;
1253
+ delete prop2.anyOf;
1254
+ delete prop2.type;
1255
+ delete prop2.minLength;
1256
+ delete prop2.maxLength;
1257
+ delete prop2.pattern;
1258
+ delete prop2.format;
1259
+ delete prop2.minimum;
1260
+ delete prop2.maximum;
1261
+ delete prop2.minItems;
1262
+ delete prop2.maxItems;
1263
+ prop2.$ref = defPointer(name);
1264
+ }
1265
+ function walkSchemaNodes(node, visitor, parentKey) {
1266
+ if (Array.isArray(node)) {
1267
+ for (const item of node) walkSchemaNodes(item, visitor, parentKey);
1268
+ return;
1269
+ }
1270
+ if (!node || typeof node !== "object") return;
1271
+ const obj = node;
1272
+ visitor(obj, parentKey);
1273
+ for (const key of Object.keys(obj)) {
1274
+ walkSchemaNodes(obj[key], visitor, key);
1275
+ }
1276
+ }
1277
+ function rewriteRefs(node, from, to) {
1278
+ walkSchemaNodes(node, (obj, key) => {
1279
+ if (key !== "$ref" && obj.$ref === from) obj.$ref = to;
1280
+ });
1281
+ }
1282
+ function countRefUsages(schema, name) {
1283
+ let count = 0;
1284
+ const pointers = /* @__PURE__ */ new Set([`#/$defs/${name}`, `#/definitions/${name}`]);
1285
+ walkSchemaNodes(schema, (obj) => {
1286
+ if (typeof obj.$ref === "string" && pointers.has(obj.$ref)) count++;
1287
+ });
1288
+ return count;
1289
+ }
1290
+ function addDefinition(model, name) {
1291
+ const schema = clone(model.schema);
1292
+ if (!schema.$defs) schema.$defs = {};
1293
+ const key = uniqueKey(name ?? "Definition", schema.$defs);
1294
+ schema.$defs[key] = { type: "object", properties: {} };
1295
+ return {
1296
+ model: { schema, uiSchema: model.uiSchema },
1297
+ path: [def(key)]
1298
+ };
1299
+ }
1300
+ function renameDefinition(model, oldName, newName) {
1301
+ const path = [def(oldName)];
1302
+ if (!newName || newName === oldName || isReservedKey(newName))
1303
+ return { model, path };
1304
+ const schema = clone(model.schema);
1305
+ const defs = schema.$defs ?? schema.definitions;
1306
+ if (!defs || !(oldName in defs) || newName in defs) return { model, path };
1307
+ renameKeyInPlace(defs, oldName, newName);
1308
+ rewriteRefs(schema, `#/$defs/${oldName}`, `#/$defs/${newName}`);
1309
+ rewriteRefs(schema, `#/definitions/${oldName}`, `#/$defs/${newName}`);
1310
+ return {
1311
+ model: { schema, uiSchema: model.uiSchema },
1312
+ path: [def(newName)]
1313
+ };
1314
+ }
1315
+ function removeDefinition(model, name) {
1316
+ const schema = clone(model.schema);
1317
+ const inDefs = schema.$defs && name in schema.$defs;
1318
+ const inDefs7 = schema.definitions && name in schema.definitions;
1319
+ if (!inDefs && !inDefs7) return model;
1320
+ if (countRefUsages(schema, name) > 0) return model;
1321
+ if (inDefs) {
1322
+ delete schema.$defs[name];
1323
+ if (Object.keys(schema.$defs).length === 0) delete schema.$defs;
1324
+ } else {
1325
+ delete schema.definitions[name];
1326
+ if (Object.keys(schema.definitions).length === 0)
1327
+ delete schema.definitions;
1328
+ }
1329
+ return { schema, uiSchema: model.uiSchema };
1330
+ }
1331
+ function definitionUsageCount(schema, name) {
1332
+ return countRefUsages(schema, name);
1333
+ }
1334
+ function normalizeChoices(choices) {
1335
+ const seen = /* @__PURE__ */ new Set();
1336
+ const clean = [];
1337
+ for (const c of choices) {
1338
+ const value = c.value.trim();
1339
+ if (!value || seen.has(value)) continue;
1340
+ seen.add(value);
1341
+ clean.push({ value, label: c.label?.trim() || void 0 });
1342
+ }
1343
+ return clean;
1344
+ }
1345
+ function withEnumNames(entry, choices) {
1346
+ const enumNames = {};
1347
+ for (const c of choices) if (c.label) enumNames[c.value] = c.label;
1348
+ const next = { ...entry ?? {} };
1349
+ const options = {
1350
+ ...next["x-ui-options"] ?? {}
1351
+ };
1352
+ if (Object.keys(enumNames).length) options.enumNames = enumNames;
1353
+ else delete options.enumNames;
1354
+ if (Object.keys(options).length) next["x-ui-options"] = options;
1355
+ else delete next["x-ui-options"];
1356
+ return Object.keys(next).length ? next : void 0;
1357
+ }
1358
+ function setEnum(model, path, choices) {
1359
+ if (path.length === 0) return model;
1360
+ const schema = clone(model.schema);
1361
+ const prop2 = getProperty(schema, path);
1362
+ if (!prop2) return model;
1363
+ const clean = normalizeChoices(choices);
1364
+ if (clean.length === 0) delete prop2.enum;
1365
+ else prop2.enum = clean.map((c) => c.value);
1366
+ const nextEntry = withEnumNames(
1367
+ getUiEntryAtPath(model.uiSchema, path),
1368
+ clean
1369
+ );
1370
+ const uiSchema = setEntryIn(model.uiSchema, propKeys(path), nextEntry);
1371
+ return { schema, uiSchema };
1372
+ }
1373
+ function readEnumChoices(prop2, uiEntry) {
1374
+ if (!Array.isArray(prop2.enum)) return [];
1375
+ const enumNames = uiEntry?.["x-ui-options"]?.enumNames ?? {};
1376
+ return prop2.enum.map((v) => {
1377
+ const value = String(v);
1378
+ return { value, label: enumNames[value] };
1379
+ });
1380
+ }
1381
+ function setTableHints(model, path, hints) {
1382
+ if (path.length === 0) return model;
1383
+ const schema = clone(model.schema);
1384
+ const prop2 = getProperty(schema, path);
1385
+ if (!prop2) return model;
1386
+ if (hints.cell) prop2["x-ui-cell"] = hints.cell;
1387
+ else delete prop2["x-ui-cell"];
1388
+ if (hints.cellOptions && Object.keys(hints.cellOptions).length > 0) {
1389
+ prop2["x-ui-cell-options"] = hints.cellOptions;
1390
+ } else {
1391
+ delete prop2["x-ui-cell-options"];
1392
+ }
1393
+ if (hints.column && Object.keys(hints.column).length > 0) {
1394
+ prop2["x-ui-column"] = hints.column;
1395
+ } else {
1396
+ delete prop2["x-ui-column"];
1397
+ }
1398
+ return { schema, uiSchema: model.uiSchema };
1399
+ }
1400
+ function readTableHints(prop2) {
1401
+ return {
1402
+ cell: typeof prop2["x-ui-cell"] === "string" ? prop2["x-ui-cell"] : void 0,
1403
+ cellOptions: prop2["x-ui-cell-options"],
1404
+ column: prop2["x-ui-column"]
1405
+ };
1406
+ }
1407
+ function getUiEntryAtPath(uiSchema, path) {
1408
+ const keys = propKeys(path);
1409
+ if (keys.length === 0) return void 0;
1410
+ let entry = uiSchema[keys[0]];
1411
+ for (let i = 1; i < keys.length && entry; i++) {
1412
+ const fields = entry["x-ui-fields"];
1413
+ entry = fields?.[keys[i]];
1414
+ }
1415
+ return entry;
1416
+ }
1417
+ function setEntryIn(map, keys, entry) {
1418
+ const [head, ...rest] = keys;
1419
+ const next = { ...map };
1420
+ if (rest.length === 0) {
1421
+ if (entry && Object.keys(entry).length > 0) next[head] = entry;
1422
+ else delete next[head];
1423
+ return next;
1424
+ }
1425
+ const current = { ...next[head] ?? {} };
1426
+ const childFields = current["x-ui-fields"] ?? {};
1427
+ const updatedFields = setEntryIn(childFields, rest, entry);
1428
+ if (Object.keys(updatedFields).length > 0) {
1429
+ current["x-ui-fields"] = updatedFields;
1430
+ } else {
1431
+ delete current["x-ui-fields"];
1432
+ }
1433
+ if (Object.keys(current).length > 0) next[head] = current;
1434
+ else delete next[head];
1435
+ return next;
1436
+ }
1437
+ function setUiEntry(model, path, entry) {
1438
+ if (path.length === 0) return model;
1439
+ const uiSchema = setEntryIn(model.uiSchema, propKeys(path), entry);
1440
+ return { schema: model.schema, uiSchema };
1441
+ }
1442
+ function setPropertyLayout(model, path, layout) {
1443
+ if (path.length === 0) return model;
1444
+ const schema = clone(model.schema);
1445
+ const prop2 = getProperty(schema, path);
1446
+ if (!prop2) return model;
1447
+ const target = prop2.type === "array" ? prop2.items : prop2;
1448
+ if (!target) return model;
1449
+ if (layout) target["x-ui-layout"] = layout;
1450
+ else delete target["x-ui-layout"];
1451
+ return { schema, uiSchema: model.uiSchema };
1452
+ }
1453
+ function setRootLayout(model, layout) {
1454
+ const uiSchema = { ...model.uiSchema };
1455
+ if (layout) uiSchema["x-ui-layout"] = layout;
1456
+ else delete uiSchema["x-ui-layout"];
1457
+ return { schema: model.schema, uiSchema };
1458
+ }
1459
+ function setRootLayoutRows(model, rows) {
1460
+ const uiSchema = { ...model.uiSchema };
1461
+ const layout = { ...uiSchema["x-ui-layout"] ?? {} };
1462
+ if (rows && rows.length > 0) layout.rows = rows;
1463
+ else delete layout.rows;
1464
+ if (Object.keys(layout).length > 0) uiSchema["x-ui-layout"] = layout;
1465
+ else delete uiSchema["x-ui-layout"];
1466
+ return { schema: model.schema, uiSchema };
1467
+ }
1468
+ function setScopedLayoutRows(model, scopePath, rows) {
1469
+ if (scopePath.length === 0) return setRootLayoutRows(model, rows);
1470
+ const schema = clone(model.schema);
1471
+ const prop2 = getProperty(schema, scopePath);
1472
+ if (!prop2) return model;
1473
+ const target = prop2.type === "array" ? prop2.items : prop2;
1474
+ if (!target) return model;
1475
+ const layout = { ...target["x-ui-layout"] ?? {} };
1476
+ if (rows && rows.length > 0) layout.rows = rows;
1477
+ else delete layout.rows;
1478
+ if (Object.keys(layout).length > 0) target["x-ui-layout"] = layout;
1479
+ else delete target["x-ui-layout"];
1480
+ return { schema, uiSchema: model.uiSchema };
1481
+ }
1482
+ function getScopedLayout(schema, uiSchema, scopePath) {
1483
+ if (scopePath.length === 0) {
1484
+ return uiSchema["x-ui-layout"];
1485
+ }
1486
+ const prop2 = getProperty(schema, scopePath);
1487
+ const target = prop2?.type === "array" ? prop2.items : prop2;
1488
+ return target?.["x-ui-layout"];
1489
+ }
1490
+ function setScopedLayoutOptions(model, scopePath, options) {
1491
+ const current = getScopedLayout(model.schema, model.uiSchema, scopePath) ?? {};
1492
+ const merged = { ...current };
1493
+ for (const key of ["direction", "gap", "labels", "compact"]) {
1494
+ const v = options[key];
1495
+ if (v === void 0 || typeof v === "number" && Number.isNaN(v))
1496
+ delete merged[key];
1497
+ else merged[key] = v;
1498
+ }
1499
+ const layout = Object.keys(merged).length > 0 ? merged : void 0;
1500
+ if (scopePath.length === 0) return setRootLayout(model, layout);
1501
+ return setPropertyLayout(model, scopePath, layout);
1502
+ }
1503
+ function setScopedLayoutGroups(model, scopePath, groups) {
1504
+ const current = getScopedLayout(model.schema, model.uiSchema, scopePath) ?? {};
1505
+ const merged = { ...current };
1506
+ if (groups && groups.length > 0) {
1507
+ merged.groups = groups;
1508
+ delete merged.rows;
1509
+ } else {
1510
+ delete merged.groups;
1511
+ }
1512
+ const layout = Object.keys(merged).length > 0 ? merged : void 0;
1513
+ if (scopePath.length === 0) return setRootLayout(model, layout);
1514
+ return setPropertyLayout(model, scopePath, layout);
1515
+ }
1516
+ function groupsOf(model, scopePath) {
1517
+ return getScopedLayout(model.schema, model.uiSchema, scopePath)?.groups ?? [];
1518
+ }
1519
+ function addLayoutGroup(model, scopePath, label) {
1520
+ const groups = groupsOf(model, scopePath);
1521
+ const next = { label: label ?? `Group ${groups.length + 1}`, rows: [] };
1522
+ return setScopedLayoutGroups(model, scopePath, [...groups, next]);
1523
+ }
1524
+ function renameLayoutGroup(model, scopePath, groupIndex, label) {
1525
+ const groups = groupsOf(model, scopePath);
1526
+ if (!groups[groupIndex] || !label.trim()) return model;
1527
+ return setScopedLayoutGroups(
1528
+ model,
1529
+ scopePath,
1530
+ groups.map((group, i) => i === groupIndex ? { ...group, label } : group)
1531
+ );
1532
+ }
1533
+ function placeFieldInGroup(model, scopePath, key, target) {
1534
+ const groups = groupsOf(model, scopePath);
1535
+ if (!key || groups.length === 0) return model;
1536
+ const stripped = groups.map((group) => ({
1537
+ ...group,
1538
+ rows: group.rows.map((row2) => ({
1539
+ ...row2,
1540
+ fields: row2.fields.filter((f) => f !== key)
1541
+ }))
1542
+ }));
1543
+ if (!target) return setScopedLayoutGroups(model, scopePath, stripped);
1544
+ const { groupIndex, rowIndex } = target;
1545
+ const row = stripped[groupIndex]?.rows[rowIndex];
1546
+ if (!row) return setScopedLayoutGroups(model, scopePath, stripped);
1547
+ stripped[groupIndex].rows = stripped[groupIndex].rows.map(
1548
+ (r, i) => i === rowIndex ? { ...r, fields: [...r.fields, key] } : r
1549
+ );
1550
+ return setScopedLayoutGroups(model, scopePath, stripped);
1551
+ }
1552
+ function moveLayoutGroup(model, scopePath, groupIndex, direction) {
1553
+ const groups = groupsOf(model, scopePath);
1554
+ const target = groupIndex + direction;
1555
+ if (!groups[groupIndex] || target < 0 || target >= groups.length)
1556
+ return model;
1557
+ const next = [...groups];
1558
+ [next[groupIndex], next[target]] = [next[target], next[groupIndex]];
1559
+ return setScopedLayoutGroups(model, scopePath, next);
1560
+ }
1561
+ function setLayoutGroupRule(model, scopePath, groupIndex, rule) {
1562
+ const groups = groupsOf(model, scopePath);
1563
+ if (!groups[groupIndex]) return model;
1564
+ const next = groups.map((group, i) => {
1565
+ if (i !== groupIndex) return group;
1566
+ const { rule: _drop, ...rest } = group;
1567
+ return rule ? { ...rest, rule } : rest;
1568
+ });
1569
+ return setScopedLayoutGroups(model, scopePath, next);
1570
+ }
1571
+ function setScopedLayoutVariant(model, scopePath, variant) {
1572
+ const current = getScopedLayout(model.schema, model.uiSchema, scopePath) ?? {};
1573
+ const merged = { ...current };
1574
+ if (variant && variant !== "groups") merged.variant = variant;
1575
+ else delete merged.variant;
1576
+ const layout = Object.keys(merged).length > 0 ? merged : void 0;
1577
+ if (scopePath.length === 0) return setRootLayout(model, layout);
1578
+ return setPropertyLayout(model, scopePath, layout);
1579
+ }
1580
+ function removeLayoutGroup(model, scopePath, groupIndex) {
1581
+ const groups = groupsOf(model, scopePath);
1582
+ if (!groups[groupIndex]) return model;
1583
+ const remaining = groups.filter((_, i) => i !== groupIndex);
1584
+ if (remaining.length > 0) {
1585
+ return setScopedLayoutGroups(model, scopePath, remaining);
1586
+ }
1587
+ const cleared = setScopedLayoutGroups(model, scopePath, []);
1588
+ return setScopedLayoutRows(cleared, scopePath, groups[groupIndex].rows);
1589
+ }
1590
+ function setScopedGroupRows(model, scopePath, groupIndex, rows) {
1591
+ const groups = getScopedLayout(
1592
+ model.schema,
1593
+ model.uiSchema,
1594
+ scopePath
1595
+ )?.groups;
1596
+ if (!groups?.[groupIndex]) return model;
1597
+ const next = groups.map(
1598
+ (group, i) => i === groupIndex ? { ...group, rows } : group
1599
+ );
1600
+ return setScopedLayoutGroups(model, scopePath, next);
1601
+ }
1602
+ function setScopedLayoutMode(model, scopePath, mode2) {
1603
+ const layout = getScopedLayout(model.schema, model.uiSchema, scopePath) ?? {};
1604
+ const groups = layout.groups ?? [];
1605
+ const rows = layout.rows ?? [];
1606
+ if (mode2 === "groups") {
1607
+ if (groups.length > 0) return model;
1608
+ return setScopedLayoutGroups(model, scopePath, [
1609
+ { label: "Group 1", rows }
1610
+ ]);
1611
+ }
1612
+ if (groups.length === 0) return model;
1613
+ const flattened = [...rows, ...groups.flatMap((group) => group.rows)];
1614
+ const cleared = setScopedLayoutGroups(model, scopePath, []);
1615
+ return setScopedLayoutRows(cleared, scopePath, flattened);
1616
+ }
1617
+ function setGlobalOptions(model, options) {
1618
+ const uiSchema = { ...model.uiSchema };
1619
+ if (options) uiSchema["x-ui-globalOptions"] = options;
1620
+ else delete uiSchema["x-ui-globalOptions"];
1621
+ return { schema: model.schema, uiSchema };
1622
+ }
1623
+
1624
+ // src/model/view.ts
1625
+ import { isOneOf as isOneOf2 } from "@petrarca/sonnet-core/schema";
1626
+ function propertyKind(prop2) {
1627
+ if (prop2.if) return "conditional";
1628
+ if (isOneOf2(prop2)) return "oneOf";
1629
+ if (prop2.type === "object" && prop2.properties) return "object";
1630
+ if (prop2.type === "array") return "array";
1631
+ if (Array.isArray(prop2.enum) && prop2.enum.length > 0) return "enum";
1632
+ return "scalar";
1633
+ }
1634
+ function arrayCardinality(min, max) {
1635
+ if (min == null && max == null) return "";
1636
+ if (min != null && min === max) return `${min}`;
1637
+ return `${min ?? ""}..${max ?? ""}`;
1638
+ }
1639
+ function unionLabelOf(prop2) {
1640
+ const members = prop2.anyOf;
1641
+ if (Array.isArray(members) && members.length > 0 && members.every((m) => !m.if)) {
1642
+ return members.map(typeLabelOf).join(" | ");
1643
+ }
1644
+ const type = prop2.type;
1645
+ return Array.isArray(type) ? type.join(" | ") : void 0;
1646
+ }
1647
+ function typeLabelOf(prop2) {
1648
+ if (prop2.if) return "if/then/else";
1649
+ if (isOneOf2(prop2)) return "oneOf";
1650
+ const union = unionLabelOf(prop2);
1651
+ if (union) return union;
1652
+ if (prop2.type === "array") {
1653
+ const itemType = prop2.items?.type ?? "any";
1654
+ return `${itemType}[${arrayCardinality(prop2.minItems, prop2.maxItems)}]`;
1655
+ }
1656
+ return prop2.type ?? "any";
1657
+ }
1658
+ function orderedKeys(properties, order) {
1659
+ const keys = Object.keys(properties);
1660
+ if (!order || order.length === 0) return keys;
1661
+ const inOrder = order.filter((k) => k in properties);
1662
+ const rest = keys.filter((k) => !inOrder.includes(k));
1663
+ return [...inOrder, ...rest];
1664
+ }
1665
+ function requiredOf(prop2) {
1666
+ if (prop2.type === "object") return prop2.required;
1667
+ if (prop2.type === "array") return prop2.items?.required;
1668
+ return void 0;
1669
+ }
1670
+ function orderOf(prop2) {
1671
+ if (prop2.type === "array") return prop2.items?.["x-ui-order"];
1672
+ return prop2["x-ui-order"];
1673
+ }
1674
+ function nodeKind(slot2, prop2) {
1675
+ if (slot2.kind === "branch") return "object";
1676
+ if (slot2.segment.kind === "def") return "definition";
1677
+ if (slot2.segment.kind === "slot") return "object";
1678
+ return propertyKind(prop2);
1679
+ }
1680
+ function requiredForChildren(prop2, isSlot) {
1681
+ return isSlot ? prop2.required : requiredOf(prop2);
1682
+ }
1683
+ function resolveRefTarget(prop2, resolve) {
1684
+ if (!prop2.$ref || !resolve) return void 0;
1685
+ const target = resolve(prop2.$ref);
1686
+ if (!target) return void 0;
1687
+ const { $ref: _drop, ...overrides } = prop2;
1688
+ return { ...target, ...overrides };
1689
+ }
1690
+ function refShapeOf(slot2, prop2, resolve) {
1691
+ const refTarget = resolveRefTarget(prop2, resolve);
1692
+ if (refTarget) {
1693
+ return {
1694
+ kind: "reference",
1695
+ typeLabel: refLabel(prop2.$ref),
1696
+ reference: prop2.$ref,
1697
+ container: refTarget,
1698
+ childrenFromRef: true
1699
+ };
1700
+ }
1701
+ if (prop2.type === "array" && prop2.items?.$ref) {
1702
+ const itemTarget = resolveRefTarget(prop2.items, resolve);
1703
+ if (itemTarget) {
1704
+ return {
1705
+ kind: "array",
1706
+ typeLabel: arrayItemLabel(prop2.items.$ref, prop2),
1707
+ reference: prop2.items.$ref,
1708
+ container: itemTarget,
1709
+ childrenFromRef: true
1710
+ };
1711
+ }
1712
+ }
1713
+ return {
1714
+ kind: nodeKind(slot2, prop2),
1715
+ typeLabel: typeLabelOf(prop2),
1716
+ container: prop2,
1717
+ childrenFromRef: false
1718
+ };
1719
+ }
1720
+ function buildNode(slot2, parentPath, required, resolve, inRef) {
1721
+ const path = [...parentPath, slot2.segment];
1722
+ const prop2 = slot2.property;
1723
+ const widget = typeof prop2["x-ui-widget"] === "string" ? prop2["x-ui-widget"] : void 0;
1724
+ const ref = refShapeOf(slot2, prop2, resolve);
1725
+ const node = {
1726
+ path,
1727
+ key: slot2.label,
1728
+ property: prop2,
1729
+ kind: ref.kind,
1730
+ slotKind: slot2.kind,
1731
+ required,
1732
+ typeLabel: ref.typeLabel,
1733
+ widget,
1734
+ reference: ref.reference,
1735
+ readOnly: inRef || void 0,
1736
+ children: []
1737
+ };
1738
+ if (isContainer(ref.container)) {
1739
+ const isSlot = slot2.segment.kind === "slot";
1740
+ const req = requiredForChildren(ref.container, isSlot);
1741
+ const childrenInRef = inRef || ref.childrenFromRef;
1742
+ node.children = orderedSlots(ref.container).map(
1743
+ (child) => buildNode(
1744
+ child,
1745
+ path,
1746
+ child.kind === "property" ? !!req?.includes(child.label) : false,
1747
+ resolve,
1748
+ childrenInRef
1749
+ )
1750
+ );
1751
+ }
1752
+ return node;
1753
+ }
1754
+ function refName(ref) {
1755
+ const local = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
1756
+ return local ? local[1] : ref.split("/").pop() || ref;
1757
+ }
1758
+ function refLabel(ref) {
1759
+ return `\u2192 ${refName(ref)}`;
1760
+ }
1761
+ function arrayItemLabel(ref, prop2) {
1762
+ return `${refName(ref)}[${arrayCardinality(prop2.minItems, prop2.maxItems)}]`;
1763
+ }
1764
+ function orderedSlots(prop2) {
1765
+ const slots = childSlots(prop2);
1766
+ const propertySlots = slots.filter((s) => s.kind === "property");
1767
+ const otherSlots = slots.filter((s) => s.kind !== "property");
1768
+ const order = orderOf(prop2);
1769
+ if (propertySlots.length === 0 || !order || order.length === 0) return slots;
1770
+ const byLabel = new Map(propertySlots.map((s) => [s.label, s]));
1771
+ const inOrder = order.filter((k) => byLabel.has(k)).map((k) => byLabel.get(k));
1772
+ const rest = propertySlots.filter((s) => !order.includes(s.label));
1773
+ return [...inOrder, ...rest, ...otherSlots];
1774
+ }
1775
+ function buildPropertyTree(schema, resolve) {
1776
+ const effective = withInDocumentFallback(schema, resolve);
1777
+ const rootProp = schemaAsRootProp(schema);
1778
+ return orderedSlots(rootProp).map(
1779
+ (slot2) => buildNode(
1780
+ slot2,
1781
+ [],
1782
+ !!schema.required?.includes(slot2.label),
1783
+ effective,
1784
+ false
1785
+ )
1786
+ );
1787
+ }
1788
+ function withInDocumentFallback(schema, resolve) {
1789
+ const defs = schema.$defs ?? schema.definitions;
1790
+ if (!defs) return resolve;
1791
+ const local = (ref) => {
1792
+ const m = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
1793
+ return m ? defs[m[1]] : void 0;
1794
+ };
1795
+ return resolve ? (ref) => resolve(ref) ?? local(ref) : local;
1796
+ }
1797
+ function layoutScopes(schema) {
1798
+ const scopes = [{ path: [], label: "Form (root)" }];
1799
+ const walk = (nodes, labelPrefix) => {
1800
+ for (const node of nodes) {
1801
+ const allProps = node.path.every((s) => s.kind === "prop");
1802
+ if (allProps && (node.kind === "object" || node.kind === "array")) {
1803
+ const labels = [...labelPrefix, node.key];
1804
+ const suffix = node.kind === "array" ? " (items)" : "";
1805
+ scopes.push({
1806
+ path: node.path,
1807
+ label: labels.join(" \u203A ") + suffix
1808
+ });
1809
+ walk(node.children, labels);
1810
+ }
1811
+ }
1812
+ };
1813
+ walk(buildPropertyTree(schema), []);
1814
+ return scopes;
1815
+ }
1816
+
1817
+ // src/questionnaire/showIf.ts
1818
+ import {
1819
+ bindingOf as bindingOf2,
1820
+ codeToken,
1821
+ CODEABLE_CONCEPT_TYPE_ID,
1822
+ CODING_TYPE_ID,
1823
+ parseCodeToken,
1824
+ valueRefOf as valueRefOf3
1825
+ } from "@petrarca/sonnet-core/schema";
1826
+
1827
+ // src/model/conditions.ts
1828
+ var CONDITION_OPERATORS = [
1829
+ "=",
1830
+ "!=",
1831
+ ">",
1832
+ ">=",
1833
+ "<",
1834
+ "<=",
1835
+ "exists"
1836
+ ];
1837
+ var CONDITION_OPERATOR_LABELS = {
1838
+ "=": "equals",
1839
+ "!=": "not equals",
1840
+ ">": "greater than",
1841
+ ">=": "greater or equal",
1842
+ "<": "less than",
1843
+ "<=": "less or equal",
1844
+ exists: "exists"
1845
+ };
1846
+ var OPERATOR_OPTIONS = CONDITION_OPERATORS.map((op) => ({
1847
+ value: op,
1848
+ label: CONDITION_OPERATOR_LABELS[op]
1849
+ }));
1850
+ var NUMERIC_OPS = /* @__PURE__ */ new Set([
1851
+ ">",
1852
+ ">=",
1853
+ "<",
1854
+ "<="
1855
+ ]);
1856
+ function coerceValue(value) {
1857
+ if (value.trim() === "") return value;
1858
+ const n = Number(value);
1859
+ return Number.isFinite(n) ? n : value;
1860
+ }
1861
+ function coerceForScope(value, operator, type) {
1862
+ if (NUMERIC_OPS.has(operator)) return coerceValue(value);
1863
+ if (type === "boolean") return value === "true";
1864
+ if (type === "number" || type === "integer") return coerceValue(value);
1865
+ return value;
1866
+ }
1867
+ function operatorToConstraint(operator, value, type) {
1868
+ const v = coerceForScope(value, operator, type);
1869
+ switch (operator) {
1870
+ case "=":
1871
+ return { const: v };
1872
+ case "!=":
1873
+ return { not: { const: v } };
1874
+ case ">":
1875
+ return { exclusiveMinimum: v };
1876
+ case ">=":
1877
+ return { minimum: v };
1878
+ case "<":
1879
+ return { exclusiveMaximum: v };
1880
+ case "<=":
1881
+ return { maximum: v };
1882
+ case "exists":
1883
+ return {};
1884
+ }
1885
+ }
1886
+ function conditionToIf(condition2, type) {
1887
+ const { field, operator } = condition2;
1888
+ if (!field) return void 0;
1889
+ if (operator === "exists") {
1890
+ return { required: [field] };
1891
+ }
1892
+ return {
1893
+ properties: {
1894
+ [field]: operatorToConstraint(operator, condition2.value, type)
1895
+ },
1896
+ required: [field]
1897
+ };
1898
+ }
1899
+ function constraintToOperator(c) {
1900
+ if (c.not && "const" in c.not)
1901
+ return { operator: "!=", value: String(c.not.const) };
1902
+ if (c.exclusiveMinimum !== void 0)
1903
+ return { operator: ">", value: String(c.exclusiveMinimum) };
1904
+ if (c.minimum !== void 0)
1905
+ return { operator: ">=", value: String(c.minimum) };
1906
+ if (c.exclusiveMaximum !== void 0)
1907
+ return { operator: "<", value: String(c.exclusiveMaximum) };
1908
+ if (c.maximum !== void 0)
1909
+ return { operator: "<=", value: String(c.maximum) };
1910
+ if ("const" in c) return { operator: "=", value: String(c.const) };
1911
+ return { operator: "exists", value: "" };
1912
+ }
1913
+ function ifToCondition(ifSchema) {
1914
+ const empty = { field: "", operator: "=", value: "" };
1915
+ if (!ifSchema) return empty;
1916
+ const props = ifSchema.properties ?? {};
1917
+ const field = Object.keys(props)[0] ?? ifSchema.required?.[0] ?? "";
1918
+ if (!field) return empty;
1919
+ const constraint = props[field];
1920
+ if (!constraint) return { field, operator: "exists", value: "" };
1921
+ const { operator, value } = constraintToOperator(constraint);
1922
+ return { field, operator, value };
1923
+ }
1924
+
1925
+ // src/questionnaire/showIf.ts
1926
+ function codedKind(field) {
1927
+ const ref = field && valueRefOf3(field)?.ref;
1928
+ if (ref === CODING_TYPE_ID) return "coding";
1929
+ return ref === CODEABLE_CONCEPT_TYPE_ID ? "concept" : void 0;
1930
+ }
1931
+ function codingIs(token, field) {
1932
+ const { system, code } = parseCodeToken(token, bindingOf2(field)?.system);
1933
+ return {
1934
+ type: "object",
1935
+ properties: {
1936
+ ...system ? { system: { const: system } } : {},
1937
+ code: { const: code }
1938
+ },
1939
+ required: ["code"]
1940
+ };
1941
+ }
1942
+ function codedIs(c, field, kind) {
1943
+ if (kind === "coding")
1944
+ return c.asText ? { const: c.value } : codingIs(c.value, field);
1945
+ return c.asText ? { properties: { text: { const: c.value } }, required: ["text"] } : {
1946
+ properties: { codings: { contains: codingIs(c.value, field) } },
1947
+ required: ["codings"]
1948
+ };
1949
+ }
1950
+ function conditionIf(c, field) {
1951
+ const kind = codedKind(field);
1952
+ if (!field || !kind || c.operator !== "=" && c.operator !== "!=")
1953
+ return conditionToIf(c, field?.type);
1954
+ const one = codedIs(c, field, kind);
1955
+ const is = valueRefOf3(field)?.multiple ? { contains: one } : one;
1956
+ return {
1957
+ properties: { [c.field]: c.operator === "=" ? is : { not: is } },
1958
+ required: [c.field]
1959
+ };
1960
+ }
1961
+ function codeOf(is, field) {
1962
+ const code = is.properties?.code?.const;
1963
+ if (typeof code !== "string") return void 0;
1964
+ const system = is.properties?.system?.const;
1965
+ return codeToken(
1966
+ { system: typeof system === "string" ? system : void 0, code },
1967
+ bindingOf2(field)?.system
1968
+ );
1969
+ }
1970
+ function asCodingConstraint(one) {
1971
+ const text = one?.properties?.text?.const;
1972
+ if (typeof text === "string") return { const: text };
1973
+ return one?.properties?.codings?.contains;
1974
+ }
1975
+ function codedOf(constraint, field, kind) {
1976
+ const one = kind === "concept" ? asCodingConstraint(constraint) : constraint;
1977
+ if (!one) return void 0;
1978
+ if (typeof one.const === "string") return { value: one.const, asText: true };
1979
+ const value = codeOf(one, field);
1980
+ return value === void 0 ? void 0 : { value };
1981
+ }
1982
+ function readCoding2(constraint, field) {
1983
+ const kind = codedKind(field);
1984
+ if (!field || !kind) return void 0;
1985
+ const negated = constraint.not;
1986
+ const is = negated ?? constraint;
1987
+ const one = valueRefOf3(field)?.multiple ? is.contains : is;
1988
+ const read = codedOf(one, field, kind);
1989
+ return read && { operator: negated ? "!=" : "=", ...read };
1990
+ }
1991
+ function allIf(conditions, typeOf) {
1992
+ const properties = {};
1993
+ const required = [];
1994
+ for (const c of conditions) {
1995
+ const one = conditionIf(c, typeOf(c.field));
1996
+ Object.assign(properties, one?.properties ?? {});
1997
+ for (const r of one?.required ?? [])
1998
+ if (!required.includes(r)) required.push(r);
1999
+ }
2000
+ return {
2001
+ ...Object.keys(properties).length ? { properties } : {},
2002
+ required
2003
+ };
2004
+ }
2005
+ function showIfToIf(showIf, typeOf) {
2006
+ if (showIf.behavior === "any" && showIf.conditions.length > 1) {
2007
+ return { anyOf: showIf.conditions.map((c) => allIf([c], typeOf)) };
2008
+ }
2009
+ return allIf(showIf.conditions, typeOf);
2010
+ }
2011
+ function readAll(ifSchema, typeOf) {
2012
+ const props = ifSchema.properties ?? {};
2013
+ const fields = [
2014
+ .../* @__PURE__ */ new Set([...Object.keys(props), ...ifSchema.required ?? []])
2015
+ ];
2016
+ return fields.map((field) => {
2017
+ const constraint = props[field];
2018
+ if (!constraint) return { field, operator: "exists", value: "" };
2019
+ return {
2020
+ field,
2021
+ ...readCoding2(constraint, typeOf(field)) ?? constraintToOperator(constraint)
2022
+ };
2023
+ });
2024
+ }
2025
+ function readShowIf(ifSchema, typeOf) {
2026
+ if (Array.isArray(ifSchema.anyOf)) {
2027
+ return {
2028
+ behavior: "any",
2029
+ conditions: ifSchema.anyOf.flatMap((m) => readAll(m, typeOf))
2030
+ };
2031
+ }
2032
+ return { behavior: "all", conditions: readAll(ifSchema, typeOf) };
2033
+ }
2034
+ function canonical(value) {
2035
+ return JSON.stringify(
2036
+ value,
2037
+ (_, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(
2038
+ Object.keys(v).sort().map((k) => [k, v[k]])
2039
+ ) : v
2040
+ );
2041
+ }
2042
+ function readConditional(member, sectionKeys, typeOf) {
2043
+ const { if: ifSchema, then } = member;
2044
+ if (!ifSchema || !then?.properties || Object.keys(member).some((k) => k !== "if" && k !== "then"))
2045
+ return null;
2046
+ if (Object.keys(then).some((k) => k !== "properties" && k !== "required"))
2047
+ return null;
2048
+ const showIf = readShowIf(ifSchema, typeOf);
2049
+ if (showIf.conditions.length === 0) return null;
2050
+ if (showIf.conditions.some((c) => !sectionKeys.has(c.field))) return null;
2051
+ if (canonical(showIfToIf(showIf, typeOf)) !== canonical(ifSchema))
2052
+ return null;
2053
+ return { showIf, properties: then.properties, required: then.required ?? [] };
2054
+ }
2055
+
2056
+ // src/questionnaire/reasons.ts
2057
+ var ABSENT_KEY = "absent";
2058
+ var ABSENT_WIDGET = "absent-reasons";
2059
+ function absentProperty(reasons) {
2060
+ return {
2061
+ type: "object",
2062
+ title: "Not answered",
2063
+ additionalProperties: { type: "string", enum: reasons.map((r) => r.value) },
2064
+ "x-ui-widget": ABSENT_WIDGET,
2065
+ // A pick is final: the reasons apply at once, also in a deferred form.
2066
+ "x-ui-options": {
2067
+ enumNames: Object.fromEntries(reasons.map((r) => [r.value, r.label])),
2068
+ immediate: true
2069
+ }
2070
+ };
2071
+ }
2072
+ function readReasons(prop2) {
2073
+ const values = prop2?.additionalProperties?.enum;
2074
+ if (!prop2 || !Array.isArray(values)) return void 0;
2075
+ const names = prop2["x-ui-options"]?.enumNames ?? {};
2076
+ const reasons = values.map((v) => ({
2077
+ value: String(v),
2078
+ label: names[String(v)] ?? String(v)
2079
+ }));
2080
+ return JSON.stringify(prop2) === JSON.stringify(absentProperty(reasons)) ? reasons : void 0;
2081
+ }
2082
+ function unlessReasonMember(key) {
2083
+ return {
2084
+ if: {
2085
+ not: {
2086
+ properties: { [ABSENT_KEY]: { required: [key] } },
2087
+ required: [ABSENT_KEY]
2088
+ }
2089
+ },
2090
+ then: { required: [key] }
2091
+ };
2092
+ }
2093
+ function unlessReasonKey(member) {
2094
+ const key = member.then?.required?.[0];
2095
+ return key !== void 0 && JSON.stringify(member) === JSON.stringify(unlessReasonMember(key)) ? key : void 0;
2096
+ }
2097
+ function answeredOrExcused(answers, key) {
2098
+ const value = answers?.[key];
2099
+ const answered = value !== void 0 && value !== null && value !== "" && !(Array.isArray(value) && value.length === 0);
2100
+ return answered || answers?.[ABSENT_KEY]?.[key] !== void 0;
2101
+ }
2102
+
2103
+ // src/questionnaire/classify.ts
2104
+ var SECTION_KEYWORDS = [
2105
+ "type",
2106
+ "title",
2107
+ "description",
2108
+ "properties",
2109
+ "required",
2110
+ "x-ui-order"
2111
+ ];
2112
+ var REPEATING_KEYWORDS = [
2113
+ "type",
2114
+ "title",
2115
+ "description",
2116
+ "items",
2117
+ "minItems",
2118
+ "maxItems"
2119
+ ];
2120
+ var TEXT_KEYWORDS = ["type", "title", "description"];
2121
+ var ROOT_KEYWORDS = [
2122
+ "$schema",
2123
+ "$id",
2124
+ "type",
2125
+ "title",
2126
+ "description",
2127
+ "properties",
2128
+ "required",
2129
+ "x-ui-order"
2130
+ ];
2131
+ var ROOT_UI_KEYS = ["x-ui-layout", "x-ui-globalOptions"];
2132
+ function effectiveHints(model, prop2, path) {
2133
+ return { ...embeddedHints(prop2), ...getUiEntryAtPath(model.uiSchema, path) };
2134
+ }
2135
+ function hintText(ui, key) {
2136
+ const value = ui[key];
2137
+ return typeof value === "string" ? value : void 0;
2138
+ }
2139
+ function extrasOf(prop2, known) {
2140
+ return Object.keys(prop2).filter((k) => !known.includes(k));
2141
+ }
2142
+ function optionsOf(prop2, ui, type) {
2143
+ const source = choiceSource(prop2, ui, type);
2144
+ if (!source) return void 0;
2145
+ const names = source.ui["x-ui-options"]?.enumNames ?? {};
2146
+ return source.values.map((v) => ({
2147
+ value: String(v),
2148
+ label: names[String(v)] ?? String(v)
2149
+ }));
2150
+ }
2151
+ function choiceSource(prop2, ui, type) {
2152
+ if (type === "singleChoice") return { values: prop2.enum ?? [], ui };
2153
+ if (type === "multipleChoice") return { values: prop2.items?.enum ?? [], ui };
2154
+ return type === "choiceWithText" ? innerChoiceSource(prop2, ui) : void 0;
2155
+ }
2156
+ function innerChoiceSource(prop2, ui) {
2157
+ const choice = prop2.properties?.choice;
2158
+ if (!choice) return void 0;
2159
+ const fields = ui["x-ui-fields"] ?? {};
2160
+ return {
2161
+ values: choice.enum ?? [],
2162
+ ui: { ...embeddedHints(choice), ...fields.choice }
2163
+ };
2164
+ }
2165
+ function lockReason(prop2) {
2166
+ if (prop2.$ref) return "a reference to a shared definition ($ref)";
2167
+ if (isOneOf3(prop2)) return "alternative structures (oneOf)";
2168
+ if (prop2.if) return "a condition (if / then)";
2169
+ if (Array.isArray(prop2.anyOf)) return "a union of types (anyOf)";
2170
+ if (Array.isArray(prop2.allOf)) return "combined schemas (allOf)";
2171
+ return `a ${prop2.type ?? "property"} of no answer type's shape`;
2172
+ }
2173
+ function lockedMember(member, combinator, index, path) {
2174
+ return {
2175
+ kind: "locked",
2176
+ key: `${combinator}[${index}]`,
2177
+ path: [...path, condition(index, combinator)],
2178
+ title: member.if ? "Condition" : "Combined schema",
2179
+ reason: member.if ? "a condition (if / then)" : `combined schemas (${combinator})`
2180
+ };
2181
+ }
2182
+ function containerItems(r, container, path) {
2183
+ const properties = questionProperties(container);
2184
+ const fields = containerFields(container);
2185
+ const keys = new Set(Object.keys(fields));
2186
+ const typeOf = (field) => fields[field];
2187
+ const requiredUnless = new Set(
2188
+ (container.allOf ?? []).map(unlessReasonKey).filter((k) => k !== void 0)
2189
+ );
2190
+ const isRequired = (key) => (container.required?.includes(key) ?? false) || requiredUnless.has(key);
2191
+ const byKey = /* @__PURE__ */ new Map();
2192
+ for (const [key, prop2] of Object.entries(properties)) {
2193
+ byKey.set(
2194
+ key,
2195
+ classifyItem(r, key, prop2, [...path, prop(key)], isRequired(key))
2196
+ );
2197
+ }
2198
+ const locked = [];
2199
+ (container.allOf ?? []).forEach((member, index) => {
2200
+ if (unlessReasonKey(member) !== void 0) return;
2201
+ const read = readConditional(member, keys, typeOf);
2202
+ if (!read || Object.keys(read.properties).some((k) => byKey.has(k))) {
2203
+ locked.push(lockedMember(member, "allOf", index, path));
2204
+ return;
2205
+ }
2206
+ for (const [key, prop2] of Object.entries(read.properties)) {
2207
+ const itemPath = [
2208
+ ...path,
2209
+ condition(index, "allOf"),
2210
+ slot("then"),
2211
+ prop(key)
2212
+ ];
2213
+ byKey.set(key, {
2214
+ ...classifyItem(r, key, prop2, itemPath, read.required.includes(key)),
2215
+ showIf: read.showIf
2216
+ });
2217
+ }
2218
+ });
2219
+ (container.anyOf ?? []).forEach(
2220
+ (member, index) => locked.push(lockedMember(member, "anyOf", index, path))
2221
+ );
2222
+ const order = orderedKeys(
2223
+ Object.fromEntries([...byKey.keys()].map((k) => [k, {}])),
2224
+ container["x-ui-order"]
2225
+ );
2226
+ return [...order.map((k) => byKey.get(k)), ...locked];
2227
+ }
2228
+ function questionProperties(container) {
2229
+ const { [ABSENT_KEY]: absent, ...rest } = container.properties ?? {};
2230
+ return absent && !readReasons(absent) ? { ...rest, [ABSENT_KEY]: absent } : rest;
2231
+ }
2232
+ function containerFields(container) {
2233
+ return Object.assign(
2234
+ {},
2235
+ ...(container.allOf ?? []).map((m) => m.then?.properties ?? {}),
2236
+ questionProperties(container)
2237
+ );
2238
+ }
2239
+ function containerOrder(container) {
2240
+ const conditional = (container.allOf ?? []).flatMap(
2241
+ (m) => Object.keys(m.then?.properties ?? {})
2242
+ );
2243
+ const keys = [...Object.keys(questionProperties(container)), ...conditional];
2244
+ return orderedKeys(
2245
+ Object.fromEntries(keys.map((k) => [k, {}])),
2246
+ container["x-ui-order"]
2247
+ );
2248
+ }
2249
+ function partsOf(r, prop2, path, textWhen) {
2250
+ const own = classifyItems(
2251
+ r,
2252
+ prop2.properties,
2253
+ prop2["x-ui-order"],
2254
+ prop2.required,
2255
+ path
2256
+ );
2257
+ const text = textWhen === void 0 ? void 0 : prop2.allOf?.[0]?.then?.properties?.text;
2258
+ if (!text) return own;
2259
+ const textPath = [
2260
+ ...path,
2261
+ condition(0, "allOf"),
2262
+ slot("then"),
2263
+ prop("text")
2264
+ ];
2265
+ return [
2266
+ ...own,
2267
+ classifyItem(r, "text", text, textPath, true)
2268
+ ];
2269
+ }
2270
+ function classifyItems(r, properties, order, required, path) {
2271
+ if (!properties) return [];
2272
+ return orderedKeys(properties, order).map(
2273
+ (key) => classifyItem(
2274
+ r,
2275
+ key,
2276
+ properties[key],
2277
+ [...path, prop(key)],
2278
+ required?.includes(key) ?? false
2279
+ )
2280
+ );
2281
+ }
2282
+ function classifyItem(r, key, prop2, path, required) {
2283
+ const ui = effectiveHints(r.model, prop2, path);
2284
+ const title = hintText(ui, "x-ui-title") ?? prop2.title ?? key;
2285
+ if (prop2.type === "null") {
2286
+ return {
2287
+ kind: "text",
2288
+ key,
2289
+ path,
2290
+ title: prop2.title,
2291
+ text: prop2.description,
2292
+ extras: extrasOf(prop2, TEXT_KEYWORDS)
2293
+ };
2294
+ }
2295
+ const answer = recognizeAnswerType(prop2, ui, r.types);
2296
+ if (answer)
2297
+ return questionItem(r, { key, prop: prop2, path, required, title, ui }, answer);
2298
+ const section = classifySection(r, key, prop2, path, required, title);
2299
+ return section ?? { kind: "locked", key, path, title, reason: lockReason(prop2) };
2300
+ }
2301
+ function presentationOf(ui, extras) {
2302
+ const widget = ui["x-ui-widget"];
2303
+ return typeof widget === "string" && !extras.includes("x-ui-widget") ? widget : void 0;
2304
+ }
2305
+ function questionItem(r, at, answer) {
2306
+ const { key, prop: prop2, path, required, title, ui } = at;
2307
+ const options = optionsOf(prop2, ui, answer.type);
2308
+ const textWhen = answer.type === "choiceWithText" ? choiceTextWhen(prop2) : void 0;
2309
+ const limits = limitsOf(prop2, answer.type);
2310
+ const settings = settingsOf(r, answer.type, prop2, ui);
2311
+ const parts = isContainerType(answer.type) ? partsOf(r, prop2, path, textWhen) : void 0;
2312
+ const presentation = presentationOf(ui, answer.extras);
2313
+ return {
2314
+ kind: "question",
2315
+ key,
2316
+ path,
2317
+ title,
2318
+ help: hintText(ui, "x-ui-description") ?? prop2.description,
2319
+ placeholder: hintText(ui, "x-ui-placeholder"),
2320
+ required,
2321
+ readOnly: prop2.readOnly === true,
2322
+ answerType: answer.type,
2323
+ ...presentation ? { presentation } : {},
2324
+ ...options ? { options } : {},
2325
+ ...parts ? { parts } : {},
2326
+ ...textWhen !== void 0 ? { textWhen } : {},
2327
+ ...Object.keys(limits).length ? { limits } : {},
2328
+ ...settings ? { settings } : {},
2329
+ ...defaultOf(prop2, answer.type),
2330
+ extras: answer.extras
2331
+ };
2332
+ }
2333
+ function defaultOf(prop2, type) {
2334
+ return prop2.default !== void 0 && !isContainerType(type) ? { defaultAnswer: prop2.default } : {};
2335
+ }
2336
+ function settingsOf(r, type, prop2, ui) {
2337
+ return valueTypeAnswer(type, r.types)?.settings?.read(prop2, ui);
2338
+ }
2339
+ function countOf(prop2) {
2340
+ return {
2341
+ ...prop2.minItems !== void 0 ? { minItems: prop2.minItems } : {},
2342
+ ...prop2.maxItems !== void 0 ? { maxItems: prop2.maxItems } : {}
2343
+ };
2344
+ }
2345
+ function classifySection(r, key, prop2, path, required, title) {
2346
+ if (prop2.$ref || isOneOf3(prop2) || prop2.if) return null;
2347
+ const repeats = prop2.type === "array";
2348
+ const body = repeats ? prop2.items : prop2;
2349
+ if (!body || body.type !== "object" || !body.properties) return null;
2350
+ const extras = repeats ? [
2351
+ ...extrasOf(prop2, REPEATING_KEYWORDS),
2352
+ ...extrasOf(body, SECTION_KEYWORDS).map((k) => `items.${k}`)
2353
+ ] : extrasOf(prop2, SECTION_KEYWORDS);
2354
+ return {
2355
+ kind: "section",
2356
+ key,
2357
+ path,
2358
+ title,
2359
+ description: prop2.description,
2360
+ repeats,
2361
+ required,
2362
+ ...repeats ? { limits: countOf(prop2) } : {},
2363
+ items: containerItems(r, body, path),
2364
+ extras: extras.filter(
2365
+ (k) => !["allOf", "anyOf", "items.allOf", "items.anyOf"].includes(k)
2366
+ )
2367
+ };
2368
+ }
2369
+ function rootReasons(schema) {
2370
+ return readReasons(schema.properties?.[ABSENT_KEY]);
2371
+ }
2372
+ function itemsOf(items) {
2373
+ return items.flatMap(
2374
+ (i) => i.kind === "section" ? [i, ...itemsOf(i.items)] : [i]
2375
+ );
2376
+ }
2377
+ function classifyQuestionnaire(model, types = DEFAULT_VALUE_TYPES) {
2378
+ const { schema, uiSchema } = model;
2379
+ const r = { model, types };
2380
+ const rootUi = uiSchema;
2381
+ return {
2382
+ title: schema.title,
2383
+ description: schema.description,
2384
+ items: containerItems(r, schema, []),
2385
+ ...rootReasons(schema) ? { reasons: rootReasons(schema) } : {},
2386
+ extras: [
2387
+ ...Object.keys(schema).filter(
2388
+ (k) => !ROOT_KEYWORDS.includes(k) && k !== "allOf" && k !== "anyOf"
2389
+ ),
2390
+ ...ROOT_UI_KEYS.filter((k) => rootUi[k] !== void 0).map(
2391
+ (k) => `uiSchema.${k}`
2392
+ )
2393
+ ]
2394
+ };
2395
+ }
2396
+
2397
+ // src/questionnaire/edit.ts
2398
+ var KEY_RE = /^[a-z][a-z0-9_]*$/;
2399
+ var MAX_KEY_LENGTH = 40;
2400
+ function keyFromText(text, taken) {
2401
+ const base = text.replace(/ß/g, "ss").normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/^(\d)/, "q_$1").slice(0, MAX_KEY_LENGTH).replace(/_+$/, "") || "question";
2402
+ const used = new Set(taken);
2403
+ if (!used.has(base)) return base;
2404
+ let i = 2;
2405
+ while (used.has(`${base}_${i}`)) i++;
2406
+ return `${base}_${i}`;
2407
+ }
2408
+ function isValidKey(key) {
2409
+ return KEY_RE.test(key) && !isReservedKey(key);
2410
+ }
2411
+ function containerProperties(model, path) {
2412
+ if (path.length === 0) return model.schema.properties ?? {};
2413
+ const owner = getProperty(model.schema, path);
2414
+ const target = owner?.type === "array" ? owner.items : owner;
2415
+ return target?.properties ?? {};
2416
+ }
2417
+ function hintsAt(model, path) {
2418
+ const prop2 = getProperty(model.schema, path);
2419
+ return {
2420
+ ...prop2 ? embeddedHints(prop2) : {},
2421
+ ...getUiEntryAtPath(model.uiSchema, path)
2422
+ };
2423
+ }
2424
+ function replaceProperty(model, path, next) {
2425
+ const current = getProperty(model.schema, path);
2426
+ if (!current) return model;
2427
+ const patch = Object.fromEntries(
2428
+ Object.keys(current).map((k) => [k, void 0])
2429
+ );
2430
+ return updateProperty(model, path, { ...patch, ...next });
2431
+ }
2432
+ function newItemFragment(item, text) {
2433
+ if (item.kind === "text")
2434
+ return { prop: { type: "null", title: text }, ui: {} };
2435
+ if (item.kind === "section")
2436
+ return { prop: { type: "object", title: text, properties: {} }, ui: {} };
2437
+ const fragment = answerTypeFragment(item.answerType);
2438
+ return { prop: { ...fragment.prop, title: text }, ui: fragment.ui };
2439
+ }
2440
+ function addItem(model, parentPath, item, text) {
2441
+ const key = keyFromText(
2442
+ text,
2443
+ Object.keys(containerProperties(model, parentPath))
2444
+ );
2445
+ const added = addProperty(model, parentPath, "scalar");
2446
+ if (added.path === parentPath) return { model, path: parentPath };
2447
+ const renamed = renameProperty(added.model, added.path, key);
2448
+ const { prop: prop2, ui } = newItemFragment(item, text);
2449
+ const replaced = replaceProperty(renamed.model, renamed.path, prop2);
2450
+ const withUi = Object.keys(ui).length ? setUiEntry(replaced, renamed.path, ui) : replaced;
2451
+ const reasons = declaredReasons(withUi);
2452
+ if (item.kind !== "section" || !reasons)
2453
+ return { model: withUi, path: renamed.path };
2454
+ const section = { ...getProperty(withUi.schema, renamed.path) };
2455
+ section.properties = {
2456
+ ...section.properties,
2457
+ [ABSENT_KEY]: absentProperty(reasons)
2458
+ };
2459
+ return {
2460
+ model: updateProperty(withUi, renamed.path, {
2461
+ properties: section.properties
2462
+ }),
2463
+ path: renamed.path
2464
+ };
2465
+ }
2466
+ function setItemText(model, path, text) {
2467
+ return updateProperty(model, path, { title: text || void 0 });
2468
+ }
2469
+ function setHelp(model, path, help) {
2470
+ return updateProperty(model, path, { description: help || void 0 });
2471
+ }
2472
+ function setReadOnly(model, path, readOnly) {
2473
+ return updateProperty(model, path, { readOnly: readOnly || void 0 });
2474
+ }
2475
+ function setHint(model, path, key, value) {
2476
+ const entry = { ...getUiEntryAtPath(model.uiSchema, path) };
2477
+ if (value === void 0 || value === "")
2478
+ delete entry[key];
2479
+ else entry[key] = value;
2480
+ return setUiEntry(model, path, Object.keys(entry).length ? entry : void 0);
2481
+ }
2482
+ function setPresentation(model, path, widget) {
2483
+ return setHint(model, path, "x-ui-widget", widget);
2484
+ }
2485
+ var isEmptyAnswer = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
2486
+ function setDefaultAnswer(model, path, value) {
2487
+ return updateProperty(model, path, {
2488
+ default: isEmptyAnswer(value) ? void 0 : value
2489
+ });
2490
+ }
2491
+ function setPlaceholder(model, path, example) {
2492
+ return setHint(model, path, "x-ui-placeholder", example);
2493
+ }
2494
+ function setKey(model, path, key) {
2495
+ const { containerPath, key: prev } = locate(path);
2496
+ const container = containerAt(model.schema, containerPath);
2497
+ if (!isValidKey(key) || !container || containerOrder(container).includes(key))
2498
+ return { model, path };
2499
+ const renamed = renameProperty(model, path, key);
2500
+ if (renamed.path === path) return { model, path };
2501
+ const target = containerAt(renamed.model.schema, containerPath);
2502
+ if (target["x-ui-order"])
2503
+ target["x-ui-order"] = target["x-ui-order"].map(
2504
+ (k) => k === prev ? key : k
2505
+ );
2506
+ renameInConditions(target, prev, key);
2507
+ if (removeUnlessReason(target, prev))
2508
+ target.allOf = [...target.allOf ?? [], unlessReasonMember(key)];
2509
+ return renamed;
2510
+ }
2511
+ function choicePath(model, path) {
2512
+ const prop2 = getProperty(model.schema, path);
2513
+ if (prop2?.type === "object")
2514
+ return { path: [...path, prop("choice")], onItems: false };
2515
+ return { path, onItems: prop2?.type === "array" };
2516
+ }
2517
+ function setOptions(model, path, options) {
2518
+ const prop2 = getProperty(model.schema, path);
2519
+ const when = prop2?.type === "object" ? choiceTextWhen(prop2) : void 0;
2520
+ if (when !== void 0 && !options.some((o) => o.value === when)) {
2521
+ return setOptions(setChoiceTextWhen(model, path, null), path, options);
2522
+ }
2523
+ const target = choicePath(model, path);
2524
+ const choices = options.map((o) => ({
2525
+ value: o.value,
2526
+ label: o.label === o.value ? "" : o.label
2527
+ }));
2528
+ if (!target.onItems) return setEnum(model, target.path, choices);
2529
+ return setItemsOptions(model, path, options, choices);
2530
+ }
2531
+ var CHOICE_TYPES = [
2532
+ "singleChoice",
2533
+ "multipleChoice",
2534
+ "choiceWithText"
2535
+ ];
2536
+ var KEPT_HINTS = ["x-ui-title", "x-ui-description", "x-ui-placeholder"];
2537
+ function changeAnswerType(model, path, type, types = DEFAULT_VALUE_TYPES, keep = []) {
2538
+ const current = currentAnswer(model, path, types);
2539
+ if (!current || current.type === type) return { model, lost: [] };
2540
+ const { prop: prop2, options, extras } = current;
2541
+ const keepOptions = CHOICE_TYPES.includes(type) && options.length > 0;
2542
+ const limits = limitsAcross(prop2, current.type, type);
2543
+ const rewritten = rewriteAs(
2544
+ model,
2545
+ path,
2546
+ prop2,
2547
+ type,
2548
+ keepOptions ? options : [],
2549
+ [...limits.kept, ...keep]
2550
+ );
2551
+ const dropped = extras.filter((e) => !keep.includes(e));
2552
+ return {
2553
+ model: keepOptions ? setOptions(rewritten, path, options) : rewritten,
2554
+ lost: [
2555
+ ...lostSettings(model, path, dropped, options.length > 0 && !keepOptions),
2556
+ ...limits.lost,
2557
+ ...lostDefault(prop2, current.type)
2558
+ ]
2559
+ };
2560
+ }
2561
+ function lostDefault(prop2, from) {
2562
+ return from && !isContainerType(from) && prop2.default !== void 0 ? ["default answer"] : [];
2563
+ }
2564
+ function limitsAcross(prop2, from, to) {
2565
+ const limits = Object.keys(from ? limitsOf(prop2, from) : {});
2566
+ const kept = limits.filter((k) => limitsFor(to).includes(k));
2567
+ return {
2568
+ kept,
2569
+ lost: limits.filter((k) => !kept.includes(k)).map((k) => LIMIT_LABELS[k].toLowerCase())
2570
+ };
2571
+ }
2572
+ function currentAnswer(model, path, types) {
2573
+ const prop2 = getProperty(model.schema, path);
2574
+ if (!prop2) return null;
2575
+ const hints = hintsAt(model, path);
2576
+ const answer = recognizeAnswerType(prop2, hints, types);
2577
+ if (!answer) return { prop: prop2, options: [], extras: [] };
2578
+ return {
2579
+ prop: prop2,
2580
+ type: answer.type,
2581
+ options: optionsOf(prop2, hints, answer.type) ?? [],
2582
+ extras: [...answer.extras, ...settingsSet(prop2, hints, answer.type, types)]
2583
+ };
2584
+ }
2585
+ function settingsSet(prop2, hints, type, types) {
2586
+ const settings = valueTypeAnswer(type, types)?.settings;
2587
+ if (!settings) return [];
2588
+ return Object.entries(settings.read(prop2, hints)).filter(([, v]) => Array.isArray(v) ? v.length > 0 : v !== void 0).map(([k]) => (settings.fields[k]?.title ?? k).toLowerCase());
2589
+ }
2590
+ function rewriteAs(model, path, prop2, type, options, limits) {
2591
+ const fragment = answerTypeFragment(
2592
+ type,
2593
+ options.length ? options.map((o) => o.value) : void 0
2594
+ );
2595
+ const next = {
2596
+ ...fragment.prop,
2597
+ ...pick(prop2, ["title", "description", "readOnly", ...limits]),
2598
+ ...pick(prop2, KEPT_HINTS)
2599
+ };
2600
+ const ui = {
2601
+ ...fragment.ui,
2602
+ ...pick(getUiEntryAtPath(model.uiSchema, path) ?? {}, KEPT_HINTS)
2603
+ };
2604
+ return setUiEntry(
2605
+ replaceProperty(model, path, next),
2606
+ path,
2607
+ Object.keys(ui).length ? ui : void 0
2608
+ );
2609
+ }
2610
+ function lostSettings(model, path, extras, optionsDropped) {
2611
+ const childHints = getUiEntryAtPath(model.uiSchema, path)?.["x-ui-fields"] ? ["the parts' settings"] : [];
2612
+ return [...extras, ...optionsDropped ? ["options"] : [], ...childHints];
2613
+ }
2614
+ function pick(obj, keys) {
2615
+ return Object.fromEntries(
2616
+ Object.entries(obj).filter(([k]) => keys.includes(k))
2617
+ );
2618
+ }
2619
+ function setKeywords(model, path, keywords) {
2620
+ if (path.length > 0)
2621
+ return updateProperty(model, path, keywords);
2622
+ const schema = { ...model.schema };
2623
+ for (const [k, v] of Object.entries(keywords)) {
2624
+ if (v === void 0) delete schema[k];
2625
+ else schema[k] = v;
2626
+ }
2627
+ return { schema, uiSchema: model.uiSchema };
2628
+ }
2629
+ var SECTION_OWN = ["title", "description", "readOnly"];
2630
+ function setSectionRepeats(model, path, repeats) {
2631
+ const prop2 = getProperty(model.schema, path);
2632
+ if (!prop2 || prop2.type === "array" === repeats) return model;
2633
+ const outer = repeats ? prop2 : prop2.items ?? {};
2634
+ const own = pick(prop2, SECTION_OWN);
2635
+ const { type: _, ...body } = Object.fromEntries(
2636
+ Object.entries(outer).filter(([k]) => !SECTION_OWN.includes(k))
2637
+ );
2638
+ const next = repeats ? { type: "array", ...own, items: { type: "object", ...body } } : { ...own, type: "object", ...body };
2639
+ return replaceProperty(model, path, next);
2640
+ }
2641
+ function setLimit(model, path, keyword, value) {
2642
+ return updateProperty(model, path, {
2643
+ [keyword]: value === "" || value === null ? void 0 : value
2644
+ });
2645
+ }
2646
+ function setQuestionnaireText(model, text) {
2647
+ const schema = { ...model.schema };
2648
+ if (text.title !== void 0) schema.title = text.title;
2649
+ if (text.description !== void 0) {
2650
+ if (text.description) schema.description = text.description;
2651
+ else delete schema.description;
2652
+ }
2653
+ return { schema, uiSchema: model.uiSchema };
2654
+ }
2655
+ function sectionPathOf(path) {
2656
+ return locate(path).containerPath;
2657
+ }
2658
+ function locate(path) {
2659
+ const key = path[path.length - 1].key;
2660
+ const c = path[path.length - 3];
2661
+ if (path.length >= 3 && c?.kind === "condition" && c.combinator === "allOf") {
2662
+ return { containerPath: path.slice(0, -3), member: c.index, key };
2663
+ }
2664
+ return { containerPath: path.slice(0, -1), key };
2665
+ }
2666
+ function containerAt(schema, path) {
2667
+ if (path.length === 0) return schema;
2668
+ const owner = getProperty(schema, path);
2669
+ return owner?.type === "array" ? owner.items : owner;
2670
+ }
2671
+ function withoutKey(obj, list2, key) {
2672
+ const values = obj[list2]?.filter((k) => k !== key);
2673
+ if (values?.length) obj[list2] = values;
2674
+ else delete obj[list2];
2675
+ }
2676
+ function dropEmptyMember(container, index) {
2677
+ const member = container.allOf?.[index];
2678
+ if (!member || Object.keys(member.then?.properties ?? {}).length > 0) return;
2679
+ container.allOf.splice(index, 1);
2680
+ if (container.allOf.length === 0) delete container.allOf;
2681
+ }
2682
+ function setShowIf(model, path, showIf) {
2683
+ const schema = structuredClone(model.schema);
2684
+ const { containerPath, member, key } = locate(path);
2685
+ const container = containerAt(schema, containerPath);
2686
+ const item = getProperty(schema, path);
2687
+ if (!container || !item) return { model, path };
2688
+ if (rewriteOwnCondition(container, member, showIf)) {
2689
+ return { model: { schema, uiSchema: model.uiSchema }, path };
2690
+ }
2691
+ container["x-ui-order"] = containerOrder(container);
2692
+ const required = takeOut(container, member, key);
2693
+ if (required === null) return { model, path };
2694
+ const next = { schema, uiSchema: model.uiSchema };
2695
+ if (!showIf || showIf.conditions.length === 0) {
2696
+ container.properties = { ...container.properties ?? {}, [key]: item };
2697
+ if (required) container.required = [...container.required ?? [], key];
2698
+ return { model: next, path: [...containerPath, prop(key)] };
2699
+ }
2700
+ const index = putConditional(container, key, item, required, showIf);
2701
+ return {
2702
+ model: next,
2703
+ path: [
2704
+ ...containerPath,
2705
+ condition(index, "allOf"),
2706
+ slot("then"),
2707
+ prop(key)
2708
+ ]
2709
+ };
2710
+ }
2711
+ function rewriteOwnCondition(container, member, showIf) {
2712
+ const own = member === void 0 ? void 0 : container.allOf?.[member];
2713
+ if (!own || !showIf?.conditions.length || Object.keys(own.then?.properties ?? {}).length !== 1)
2714
+ return false;
2715
+ own.if = showIfToIf(showIf, (field) => containerFields(container)[field]);
2716
+ return true;
2717
+ }
2718
+ function takeOut(container, member, key) {
2719
+ const holder = member === void 0 ? container : container.allOf?.[member]?.then;
2720
+ if (!holder?.properties?.[key]) return null;
2721
+ const required = holder.required?.includes(key) ?? false;
2722
+ delete holder.properties[key];
2723
+ withoutKey(holder, "required", key);
2724
+ if (member !== void 0) dropEmptyMember(container, member);
2725
+ return required;
2726
+ }
2727
+ function putConditional(container, key, item, required, showIf) {
2728
+ const typeOf = (field) => containerFields(container)[field];
2729
+ const conditional = {
2730
+ if: showIfToIf(showIf, typeOf),
2731
+ then: {
2732
+ properties: { [key]: item },
2733
+ ...required ? { required: [key] } : {}
2734
+ }
2735
+ };
2736
+ container.allOf = [...container.allOf ?? [], conditional];
2737
+ return container.allOf.length - 1;
2738
+ }
2739
+ function moveItem(model, path, direction) {
2740
+ const schema = structuredClone(model.schema);
2741
+ const { containerPath, key } = locate(path);
2742
+ const container = containerAt(schema, containerPath);
2743
+ if (!container) return model;
2744
+ const order = containerOrder(container);
2745
+ const i = order.indexOf(key);
2746
+ const j = i + direction;
2747
+ if (i < 0 || j < 0 || j >= order.length) return model;
2748
+ [order[i], order[j]] = [order[j], order[i]];
2749
+ container["x-ui-order"] = order;
2750
+ return { schema, uiSchema: model.uiSchema };
2751
+ }
2752
+ function reorderItem(model, path, beforeKey) {
2753
+ const schema = structuredClone(model.schema);
2754
+ const { containerPath, key } = locate(path);
2755
+ const container = containerAt(schema, containerPath);
2756
+ if (!container || key === beforeKey) return model;
2757
+ const order = containerOrder(container).filter((k) => k !== key);
2758
+ const at = beforeKey === null ? order.length : order.indexOf(beforeKey);
2759
+ if (at < 0) return model;
2760
+ order.splice(at, 0, key);
2761
+ container["x-ui-order"] = order;
2762
+ return { schema, uiSchema: model.uiSchema };
2763
+ }
2764
+ function deleteItem(model, path) {
2765
+ const deleted = deleteProperty(model, path);
2766
+ if (deleted === model) return model;
2767
+ const { containerPath, member, key } = locate(path);
2768
+ const container = containerAt(deleted.schema, containerPath);
2769
+ if (!container) return deleted;
2770
+ withoutKey(container, "x-ui-order", key);
2771
+ if (member !== void 0) dropEmptyMember(container, member);
2772
+ else removeUnlessReason(container, key);
2773
+ return deleted;
2774
+ }
2775
+ function renameInConditions(container, prev, next) {
2776
+ const renameIn = (ifSchema) => {
2777
+ if (!ifSchema) return;
2778
+ if (ifSchema.properties && prev in ifSchema.properties) {
2779
+ ifSchema.properties = Object.fromEntries(
2780
+ Object.entries(ifSchema.properties).map(([k, v]) => [
2781
+ k === prev ? next : k,
2782
+ v
2783
+ ])
2784
+ );
2785
+ }
2786
+ if (ifSchema.required)
2787
+ ifSchema.required = ifSchema.required.map((k) => k === prev ? next : k);
2788
+ ifSchema.anyOf?.forEach(renameIn);
2789
+ };
2790
+ container.allOf?.forEach((m) => renameIn(m.if));
2791
+ }
2792
+ function setItemsOptions(model, path, options, choices) {
2793
+ const prop2 = getProperty(model.schema, path);
2794
+ const items = {
2795
+ ...prop2?.items ?? { type: "string" },
2796
+ enum: options.map((o) => o.value)
2797
+ };
2798
+ const withValues = updateProperty(model, path, { items });
2799
+ const labels = Object.fromEntries(
2800
+ choices.filter((c) => c.label).map((c) => [c.value, c.label])
2801
+ );
2802
+ const current = getUiEntryAtPath(withValues.uiSchema, path)?.["x-ui-options"] ?? {};
2803
+ const { enumNames: _, ...rest } = current;
2804
+ const next = Object.keys(labels).length ? { ...rest, enumNames: labels } : rest;
2805
+ return setHint(
2806
+ withValues,
2807
+ path,
2808
+ "x-ui-options",
2809
+ Object.keys(next).length ? next : void 0
2810
+ );
2811
+ }
2812
+ function setValueTypeSettings(model, path, answer, values) {
2813
+ if (!answer.settings) return model;
2814
+ const written = answer.settings.write(values);
2815
+ const withProp = updateProperty(model, path, written.prop);
2816
+ const current = getUiEntryAtPath(withProp.uiSchema, path)?.["x-ui-options"] ?? {};
2817
+ const next = { ...current };
2818
+ for (const [k, v] of Object.entries(written.options)) {
2819
+ if (v === void 0) delete next[k];
2820
+ else next[k] = v;
2821
+ }
2822
+ return setHint(
2823
+ withProp,
2824
+ path,
2825
+ "x-ui-options",
2826
+ Object.keys(next).length ? next : void 0
2827
+ );
2828
+ }
2829
+ var OTHER_OPTION = { value: "other", label: "Other" };
2830
+ function setChoiceTextWhen(model, path, when) {
2831
+ const prop2 = getProperty(model.schema, path);
2832
+ const { choice, text } = prop2 ? rawChoiceAndText(prop2) : {};
2833
+ if (!prop2 || !choice || !text) return model;
2834
+ if (when === null) {
2835
+ return updateProperty(model, path, {
2836
+ properties: { choice, text },
2837
+ allOf: void 0
2838
+ });
2839
+ }
2840
+ const { model: withOption, value } = withAskingOption(
2841
+ model,
2842
+ path,
2843
+ prop2,
2844
+ when
2845
+ );
2846
+ const nextChoice = getProperty(withOption.schema, [
2847
+ ...path,
2848
+ prop("choice")
2849
+ ]);
2850
+ return updateProperty(withOption, path, {
2851
+ properties: { choice: nextChoice },
2852
+ allOf: [otherTextMember(value, text)]
2853
+ });
2854
+ }
2855
+ function withAskingOption(model, path, prop2, when) {
2856
+ const options = optionsOf(prop2, hintsAt(model, path), "choiceWithText") ?? [];
2857
+ if (options.some((o) => o.value === when)) return { model, value: when };
2858
+ if (options.some((o) => o.value === OTHER_OPTION.value))
2859
+ return { model, value: OTHER_OPTION.value };
2860
+ return {
2861
+ model: setOptions(model, path, [...options, OTHER_OPTION]),
2862
+ value: OTHER_OPTION.value
2863
+ };
2864
+ }
2865
+ function declaredReasons(model) {
2866
+ return readReasons(model.schema.properties?.[ABSENT_KEY]);
2867
+ }
2868
+ function removeUnlessReason(container, key) {
2869
+ const index = (container.allOf ?? []).findIndex(
2870
+ (m) => unlessReasonKey(m) === key
2871
+ );
2872
+ if (index < 0) return false;
2873
+ container.allOf.splice(index, 1);
2874
+ if (container.allOf.length === 0) delete container.allOf;
2875
+ return true;
2876
+ }
2877
+ function setQuestionRequired(model, path, required) {
2878
+ const { containerPath, member, key } = locate(path);
2879
+ if (member !== void 0 || !declaredReasons(model))
2880
+ return setRequired(model, path, required);
2881
+ const schema = structuredClone(model.schema);
2882
+ const container = containerAt(schema, containerPath);
2883
+ if (!container) return model;
2884
+ removeUnlessReason(container, key);
2885
+ withoutKey(container, "required", key);
2886
+ if (required)
2887
+ container.allOf = [...container.allOf ?? [], unlessReasonMember(key)];
2888
+ return { schema, uiSchema: model.uiSchema };
2889
+ }
2890
+ function sectionPaths(items) {
2891
+ return itemsOf(items).filter((i) => i.kind === "section").map((i) => i.path);
2892
+ }
2893
+ function withReasons(container, reasons) {
2894
+ container["x-ui-order"] = containerOrder(container);
2895
+ container.properties = {
2896
+ ...container.properties ?? {},
2897
+ [ABSENT_KEY]: absentProperty(reasons)
2898
+ };
2899
+ const required = (container.required ?? []).filter((k) => k !== ABSENT_KEY);
2900
+ delete container.required;
2901
+ for (const key of required) {
2902
+ if (!(container.allOf ?? []).some((m) => unlessReasonKey(m) === key)) {
2903
+ container.allOf = [...container.allOf ?? [], unlessReasonMember(key)];
2904
+ }
2905
+ }
2906
+ }
2907
+ function withoutReasons(container) {
2908
+ if (container.properties) delete container.properties[ABSENT_KEY];
2909
+ const keys = (container.allOf ?? []).map(unlessReasonKey).filter((k) => k !== void 0);
2910
+ for (const key of keys) removeUnlessReason(container, key);
2911
+ if (keys.length)
2912
+ container.required = [...container.required ?? [], ...keys];
2913
+ }
2914
+ function setReasons(model, reasons) {
2915
+ const paths = [
2916
+ [],
2917
+ ...sectionPaths(classifyQuestionnaire(model).items)
2918
+ ];
2919
+ const schema = structuredClone(model.schema);
2920
+ for (const path of paths) {
2921
+ const container = containerAt(schema, path);
2922
+ if (!container) continue;
2923
+ if (reasons?.length) withReasons(container, reasons);
2924
+ else withoutReasons(container);
2925
+ }
2926
+ return { schema, uiSchema: model.uiSchema };
2927
+ }
2928
+
2929
+ // src/questionnaire/completion.ts
2930
+ var COMPARE = {
2931
+ "=": (a, v) => String(a) === v,
2932
+ "!=": (a, v) => String(a) !== v,
2933
+ ">": (a, v) => Number(a) > Number(v),
2934
+ ">=": (a, v) => Number(a) >= Number(v),
2935
+ "<": (a, v) => Number(a) < Number(v),
2936
+ "<=": (a, v) => Number(a) <= Number(v)
2937
+ };
2938
+ function holds(c, answers) {
2939
+ const value = answers[c.field];
2940
+ if (c.operator === "exists")
2941
+ return value !== void 0 && value !== null && value !== "";
2942
+ return value !== void 0 && value !== null && COMPARE[c.operator](value, c.value);
2943
+ }
2944
+ function isAsked(showIf, answers) {
2945
+ if (!showIf || showIf.conditions.length === 0) return true;
2946
+ const results = showIf.conditions.map((c) => holds(c, answers));
2947
+ return showIf.behavior === "any" ? results.some(Boolean) : results.every(Boolean);
2948
+ }
2949
+ function missingIn(items, answers, prefix) {
2950
+ return items.flatMap((item) => {
2951
+ if (item.kind === "text" || item.kind === "locked" || !isAsked(item.showIf, answers))
2952
+ return [];
2953
+ const at = prefix + item.key;
2954
+ if (item.kind === "question")
2955
+ return answeredOrExcused(answers, item.key) ? [] : [at];
2956
+ const value = answers[item.key];
2957
+ if (!item.repeats)
2958
+ return missingIn(
2959
+ item.items,
2960
+ value ?? {},
2961
+ `${at}.`
2962
+ );
2963
+ const entries = Array.isArray(value) ? value : [];
2964
+ return entries.flatMap(
2965
+ (entry, i) => missingIn(item.items, entry ?? {}, `${at}.${i}.`)
2966
+ );
2967
+ });
2968
+ }
2969
+ function isComplete(view, answers) {
2970
+ const missing = missingIn(view.items, answers, "");
2971
+ return { complete: missing.length === 0, missing };
2972
+ }
2973
+
2974
+ // src/questionnaire/capabilities.ts
2975
+ import { z } from "zod";
2976
+ import { warnLog } from "@petrarca/sonnet-core/logger";
2977
+ var BUILTIN_ANSWER_TYPES = Object.keys(ANSWER_TYPE_LABELS);
2978
+ var questionnaireCapabilitiesSchema = z.object({
2979
+ answerTypes: z.array(z.string()).optional().meta({
2980
+ description: "The answer types offered for a new or changed question, in order -- the built-in ones by name, a value type by its id. Default: all."
2981
+ }),
2982
+ sections: z.boolean().default(true).meta({ description: "Whether sections may be added." }),
2983
+ logic: z.boolean().default(true).meta({ description: 'Whether "show only if" is offered.' }),
2984
+ reasons: z.boolean().default(true).meta({
2985
+ description: "Whether reasons for no answer may be declared."
2986
+ }),
2987
+ switchToDesigner: z.boolean().default(true).meta({
2988
+ description: "Whether the way to the schema designer is shown (when the application offers one)."
2989
+ }),
2990
+ previews: z.array(z.enum(["form", "json", "capabilities"])).default(["form", "json"]).meta({
2991
+ description: 'The tabs of the centre pane, in order: the form, its JSON, and the profile itself ("capabilities", edited live).'
2992
+ })
2993
+ }).meta({
2994
+ id: "QuestionnaireDesignerCapabilities",
2995
+ title: "QuestionnaireDesignerCapabilities",
2996
+ description: "What a questionnaire author may build. Every field optional, defaulting to the most permissive value."
2997
+ });
2998
+ function validateQuestionnaireCapabilities(input) {
2999
+ const parsed = questionnaireCapabilitiesSchema.safeParse(input);
3000
+ if (parsed.success)
3001
+ return { ok: true, value: input };
3002
+ return {
3003
+ ok: false,
3004
+ errors: parsed.error.issues.map(
3005
+ (i) => `${i.path.length ? i.path.join(".") : "(root)"}: ${i.message}`
3006
+ )
3007
+ };
3008
+ }
3009
+ function resolveQuestionnaireCapabilities(capabilities, valueTypes = DEFAULT_VALUE_TYPES) {
3010
+ const parsed = questionnaireCapabilitiesSchema.safeParse(capabilities ?? {});
3011
+ if (!parsed.success) {
3012
+ warnLog(
3013
+ "[QuestionnaireDesigner] Invalid capabilities -- falling back to defaults.",
3014
+ parsed.error.issues
3015
+ );
3016
+ }
3017
+ const value = parsed.success ? parsed.data : questionnaireCapabilitiesSchema.parse({});
3018
+ const all = [...BUILTIN_ANSWER_TYPES, ...valueTypes.map((t) => t.type.id)];
3019
+ return { ...value, answerTypes: knownAnswerTypes(value.answerTypes, all) };
3020
+ }
3021
+ function knownAnswerTypes(named, all) {
3022
+ const known = (named ?? []).filter((t) => all.includes(t));
3023
+ const unknown = (named ?? []).filter((t) => !all.includes(t));
3024
+ if (unknown.length)
3025
+ warnLog(
3026
+ "[QuestionnaireDesigner] Unknown answer types in the profile -- ignored.",
3027
+ unknown
3028
+ );
3029
+ return known.length ? known : all;
3030
+ }
3031
+
3032
+ export {
3033
+ isReservedKey,
3034
+ prop,
3035
+ branch,
3036
+ condition,
3037
+ slot,
3038
+ def,
3039
+ pathKey,
3040
+ parsePathKey,
3041
+ sameSegment,
3042
+ samePath,
3043
+ childContainer,
3044
+ definitionsOf,
3045
+ childSlots,
3046
+ schemaAsRootProp,
3047
+ getProperty,
3048
+ isContainer,
3049
+ propertyKind,
3050
+ orderedKeys,
3051
+ buildPropertyTree,
3052
+ layoutScopes,
3053
+ newProperty,
3054
+ newBranch,
3055
+ addProperty,
3056
+ addBranch,
3057
+ removeBranch,
3058
+ moveBranch,
3059
+ newCondition,
3060
+ addConditional,
3061
+ removeConditional,
3062
+ setCondition,
3063
+ addElse,
3064
+ removeElse,
3065
+ setConditionalBehavior,
3066
+ deleteProperty,
3067
+ renameProperty,
3068
+ moveProperty,
3069
+ setRequired,
3070
+ updateProperty,
3071
+ changePropertyType,
3072
+ setReference,
3073
+ addDefinition,
3074
+ renameDefinition,
3075
+ removeDefinition,
3076
+ definitionUsageCount,
3077
+ setEnum,
3078
+ readEnumChoices,
3079
+ setTableHints,
3080
+ readTableHints,
3081
+ getUiEntryAtPath,
3082
+ setUiEntry,
3083
+ setPropertyLayout,
3084
+ setRootLayout,
3085
+ setRootLayoutRows,
3086
+ setScopedLayoutRows,
3087
+ getScopedLayout,
3088
+ setScopedLayoutOptions,
3089
+ setScopedLayoutGroups,
3090
+ addLayoutGroup,
3091
+ renameLayoutGroup,
3092
+ placeFieldInGroup,
3093
+ moveLayoutGroup,
3094
+ setLayoutGroupRule,
3095
+ setScopedLayoutVariant,
3096
+ removeLayoutGroup,
3097
+ setScopedGroupRows,
3098
+ setScopedLayoutMode,
3099
+ setGlobalOptions,
3100
+ CONDITION_OPERATORS,
3101
+ CONDITION_OPERATOR_LABELS,
3102
+ OPERATOR_OPTIONS,
3103
+ operatorToConstraint,
3104
+ conditionToIf,
3105
+ constraintToOperator,
3106
+ ifToCondition,
3107
+ ANSWER_TYPE_LIMITS,
3108
+ LIMIT_LABELS,
3109
+ limitsFor,
3110
+ limitsOf,
3111
+ OPTIONS_LIST_WIDGET,
3112
+ SHOW_IF_WIDGET,
3113
+ QUANTITY_ANSWER,
3114
+ codingBindingOf,
3115
+ CODING_ANSWER,
3116
+ CODEABLE_CONCEPT_ANSWER,
3117
+ DEFAULT_VALUE_TYPES,
3118
+ valueTypeOf,
3119
+ valueTypeAnswer,
3120
+ ANSWER_TYPE_LABELS,
3121
+ isBuiltinAnswerType,
3122
+ answerTypeLabel,
3123
+ CHOICE_WITH_TEXT_WIDGET,
3124
+ isContainerType,
3125
+ choiceTextWhen,
3126
+ otherTextMember,
3127
+ rawChoiceAndText,
3128
+ recognizeAnswerType,
3129
+ embeddedHints,
3130
+ answerTypeFragment,
3131
+ showIfToIf,
3132
+ readConditional,
3133
+ ABSENT_KEY,
3134
+ ABSENT_WIDGET,
3135
+ absentProperty,
3136
+ readReasons,
3137
+ unlessReasonMember,
3138
+ unlessReasonKey,
3139
+ answeredOrExcused,
3140
+ optionsOf,
3141
+ containerFields,
3142
+ containerOrder,
3143
+ itemsOf,
3144
+ classifyQuestionnaire,
3145
+ keyFromText,
3146
+ isValidKey,
3147
+ addItem,
3148
+ setItemText,
3149
+ setHelp,
3150
+ setReadOnly,
3151
+ setPresentation,
3152
+ setDefaultAnswer,
3153
+ setPlaceholder,
3154
+ setKey,
3155
+ setOptions,
3156
+ changeAnswerType,
3157
+ setKeywords,
3158
+ setSectionRepeats,
3159
+ setLimit,
3160
+ setQuestionnaireText,
3161
+ sectionPathOf,
3162
+ setShowIf,
3163
+ moveItem,
3164
+ reorderItem,
3165
+ deleteItem,
3166
+ setValueTypeSettings,
3167
+ OTHER_OPTION,
3168
+ setChoiceTextWhen,
3169
+ declaredReasons,
3170
+ setQuestionRequired,
3171
+ setReasons,
3172
+ isAsked,
3173
+ isComplete,
3174
+ questionnaireCapabilitiesSchema,
3175
+ validateQuestionnaireCapabilities,
3176
+ resolveQuestionnaireCapabilities
3177
+ };
3178
+ //# sourceMappingURL=chunk-MVPNMAVL.js.map