@case-framework/survey-core 0.7.0 → 0.8.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.
@@ -1,1460 +0,0 @@
1
- //#region src/utils.ts
2
- function hashString(seed) {
3
- let hash = 2166136261;
4
- for (let i = 0; i < seed.length; i++) {
5
- hash ^= seed.charCodeAt(i);
6
- hash = Math.imul(hash, 16777619);
7
- }
8
- return hash >>> 0;
9
- }
10
- function createSeededRandom(seed) {
11
- let state = hashString(seed);
12
- return () => {
13
- state = state + 1831565813 >>> 0;
14
- let next = state;
15
- next = Math.imul(next ^ next >>> 15, next | 1);
16
- next ^= next + Math.imul(next ^ next >>> 7, next | 61);
17
- return ((next ^ next >>> 14) >>> 0) / 4294967296;
18
- };
19
- }
20
- function shuffleArray(values, random = Math.random) {
21
- const shuffledValues = [...values];
22
- for (let i = shuffledValues.length - 1; i > 0; i--) {
23
- const j = Math.floor(random() * (i + 1));
24
- [shuffledValues[i], shuffledValues[j]] = [shuffledValues[j], shuffledValues[i]];
25
- }
26
- return shuffledValues;
27
- }
28
- /**
29
- * Shuffles an array of indices using the Fisher-Yates shuffle algorithm
30
- * @param length - The length of the array to create indices for
31
- * @returns A shuffled array of indices from 0 to length-1
32
- */
33
- function shuffleIndices(length, random = Math.random) {
34
- return shuffleArray(Array.from({ length }, (_, i) => i), random);
35
- }
36
- function structuredCloneMethod(obj) {
37
- if (typeof structuredClone !== "undefined") return structuredClone(obj);
38
- return JSON.parse(JSON.stringify(obj));
39
- }
40
- const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
41
- function generateId() {
42
- const length = 10;
43
- const timestamp = Date.now();
44
- let encodedTimestamp = "";
45
- let t = timestamp;
46
- const base = 62;
47
- while (t > 0) {
48
- encodedTimestamp = ALPHABET.charAt(t % base) + encodedTimestamp;
49
- t = Math.floor(t / base);
50
- }
51
- let randomPart = "";
52
- for (let i = 0; i < length; i++) randomPart += ALPHABET.charAt(Math.floor(Math.random() * base));
53
- return randomPart + encodedTimestamp;
54
- }
55
- /** Alphanumeric chars without ambiguous ones (0/O, 1/I) for human-readable coding keys. */
56
- const CODING_KEY_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
57
- /**
58
- * Generates a short random coding key suitable for item keys in exports/codebooks.
59
- * @param length - Key length (default 4)
60
- * @returns A random string of the specified length.
61
- */
62
- function generateCodingKey(length = 4) {
63
- const base = 32;
64
- let key = "";
65
- for (let i = 0; i < length; i++) key += CODING_KEY_ALPHABET.charAt(Math.floor(Math.random() * base));
66
- return key;
67
- }
68
- //#endregion
69
- //#region src/survey/utils/value-reference.ts
70
- const SEPARATOR = "...";
71
- let ValueReferenceMethod = /* @__PURE__ */ function(ValueReferenceMethod) {
72
- ValueReferenceMethod["get"] = "get";
73
- ValueReferenceMethod["isDefined"] = "isDefined";
74
- return ValueReferenceMethod;
75
- }({});
76
- var ValueReference = class ValueReference {
77
- _itemId;
78
- _name;
79
- _slotId;
80
- constructor(str) {
81
- const parts = str.split(SEPARATOR);
82
- if (parts.length !== 3) throw new Error("Invalid value reference: " + str);
83
- this._itemId = parts[0];
84
- this._name = parts[1];
85
- this._slotId = parts[2];
86
- }
87
- get itemId() {
88
- return this._itemId;
89
- }
90
- get name() {
91
- return this._name;
92
- }
93
- get slotId() {
94
- return this._slotId;
95
- }
96
- set itemId(itemId) {
97
- this._itemId = itemId;
98
- }
99
- toString() {
100
- return `${this._itemId}${SEPARATOR}${this._name}${this._slotId ? SEPARATOR + this._slotId : ""}`;
101
- }
102
- static fromParts(itemId, name, slotId) {
103
- return new ValueReference(`${itemId}${SEPARATOR}${name}${SEPARATOR}${slotId}`);
104
- }
105
- };
106
- let ReferenceUsageType = /* @__PURE__ */ function(ReferenceUsageType) {
107
- ReferenceUsageType["displayConditions"] = "displayConditions";
108
- ReferenceUsageType["templateValues"] = "templateValues";
109
- ReferenceUsageType["validations"] = "validations";
110
- ReferenceUsageType["disabledConditions"] = "disabledConditions";
111
- ReferenceUsageType["prefills"] = "prefills";
112
- return ReferenceUsageType;
113
- }({});
114
- //#endregion
115
- //#region src/expressions/expression.ts
116
- const ExpressionType = {
117
- Const: "const",
118
- ResponseVariable: "responseVariable",
119
- ContextVariable: "contextVariable",
120
- Function: "function"
121
- };
122
- const ContextVariableType = {
123
- Locale: "locale",
124
- ParticipantFlag: "participantFlag",
125
- CustomValue: "customValue",
126
- CustomExpression: "customExpression"
127
- };
128
- /**
129
- * Base class for all expressions.
130
- */
131
- var Expression = class Expression {
132
- type;
133
- editorConfig;
134
- constructor(type, editorConfig) {
135
- this.type = type;
136
- this.editorConfig = editorConfig;
137
- }
138
- static deserialize(json) {
139
- if (!json) return;
140
- switch (json.type) {
141
- case ExpressionType.Const: return ConstExpression.deserialize(json);
142
- case ExpressionType.ResponseVariable: return ResponseVariableExpression.deserialize(json);
143
- case ExpressionType.ContextVariable: return ContextVariableExpression.deserialize(json);
144
- case ExpressionType.Function: return FunctionExpression.deserialize(json);
145
- }
146
- }
147
- clone() {
148
- return Expression.deserialize(this.serialize()) ?? (() => {
149
- throw new Error("Failed to clone expression");
150
- })();
151
- }
152
- };
153
- var ConstExpression = class ConstExpression extends Expression {
154
- value;
155
- constructor(value, editorConfig) {
156
- super(ExpressionType.Const, editorConfig);
157
- this.value = value;
158
- this.type = ExpressionType.Const;
159
- }
160
- static deserialize(json) {
161
- if (json.type !== ExpressionType.Const) throw new Error("Invalid expression type: " + json.type);
162
- return new ConstExpression(json.value, json.editorConfig);
163
- }
164
- get responseVariableRefs() {
165
- return [];
166
- }
167
- serialize() {
168
- return {
169
- type: this.type,
170
- value: this.value,
171
- editorConfig: this.editorConfig
172
- };
173
- }
174
- updateItemKeyReferences(_oldItemKey, _newItemKey) {
175
- return false;
176
- }
177
- };
178
- var ResponseVariableExpression = class ResponseVariableExpression extends Expression {
179
- variableRef;
180
- constructor(variableRef, editorConfig) {
181
- super(ExpressionType.ResponseVariable, editorConfig);
182
- this.variableRef = variableRef;
183
- this.type = ExpressionType.ResponseVariable;
184
- }
185
- static deserialize(json) {
186
- if (json.type !== ExpressionType.ResponseVariable) throw new Error("Invalid expression type: " + json.type);
187
- return new ResponseVariableExpression(json.variableRef, json.editorConfig);
188
- }
189
- get responseVariableRefs() {
190
- return [new ValueReference(this.variableRef)];
191
- }
192
- get responseVariableRef() {
193
- return new ValueReference(this.variableRef);
194
- }
195
- serialize() {
196
- return {
197
- type: this.type,
198
- variableRef: this.variableRef,
199
- editorConfig: this.editorConfig
200
- };
201
- }
202
- };
203
- var ContextVariableExpression = class ContextVariableExpression extends Expression {
204
- contextType;
205
- key;
206
- arguments;
207
- asType;
208
- constructor(contextType, key, args, asType, editorConfig) {
209
- super(ExpressionType.ContextVariable, editorConfig);
210
- this.type = ExpressionType.ContextVariable;
211
- this.contextType = contextType;
212
- this.key = key;
213
- this.arguments = args;
214
- this.asType = asType;
215
- }
216
- static deserialize(json) {
217
- if (json.type !== ExpressionType.ContextVariable) throw new Error("Invalid expression type: " + json.type);
218
- return new ContextVariableExpression(json.contextType, Expression.deserialize(json.key), json.arguments?.map((arg) => Expression.deserialize(arg)), json.asType, json.editorConfig);
219
- }
220
- get responseVariableRefs() {
221
- return this.arguments?.flatMap((arg) => arg?.responseVariableRefs).filter((ref) => ref !== void 0) ?? [];
222
- }
223
- serialize() {
224
- return {
225
- type: this.type,
226
- contextType: this.contextType,
227
- key: this.key?.serialize(),
228
- arguments: this.arguments?.map((arg) => arg?.serialize()),
229
- asType: this.asType,
230
- editorConfig: this.editorConfig
231
- };
232
- }
233
- };
234
- let FunctionExpressionNames = /* @__PURE__ */ function(FunctionExpressionNames) {
235
- FunctionExpressionNames["and"] = "and";
236
- FunctionExpressionNames["or"] = "or";
237
- FunctionExpressionNames["not"] = "not";
238
- FunctionExpressionNames["list_contains"] = "list_contains";
239
- FunctionExpressionNames["eq"] = "eq";
240
- FunctionExpressionNames["gt"] = "gt";
241
- FunctionExpressionNames["gte"] = "gte";
242
- FunctionExpressionNames["lt"] = "lt";
243
- FunctionExpressionNames["lte"] = "lte";
244
- FunctionExpressionNames["in_range"] = "in_range";
245
- FunctionExpressionNames["sum"] = "sum";
246
- FunctionExpressionNames["min"] = "min";
247
- FunctionExpressionNames["max"] = "max";
248
- FunctionExpressionNames["str_eq"] = "str_eq";
249
- FunctionExpressionNames["date_eq"] = "date_eq";
250
- return FunctionExpressionNames;
251
- }({});
252
- var FunctionExpression = class FunctionExpression extends Expression {
253
- functionName;
254
- arguments;
255
- constructor(functionName, args, editorConfig) {
256
- super(ExpressionType.Function);
257
- this.type = ExpressionType.Function;
258
- this.functionName = functionName;
259
- this.arguments = args;
260
- this.editorConfig = editorConfig;
261
- }
262
- static deserialize(json) {
263
- if (json.type !== ExpressionType.Function) throw new Error("Invalid expression type: " + json.type);
264
- const functionName = json.functionName;
265
- if (!Object.values(FunctionExpressionNames).includes(functionName)) throw new Error("Invalid function name: " + functionName);
266
- const expr = new FunctionExpression(functionName, json.arguments.map((arg) => Expression.deserialize(arg)));
267
- expr.editorConfig = json.editorConfig;
268
- return expr;
269
- }
270
- get responseVariableRefs() {
271
- const refStrings = this.arguments.flatMap((arg) => arg?.responseVariableRefs).filter((ref) => ref !== void 0).map((ref) => ref.toString());
272
- return [...new Set(refStrings)].map((ref) => new ValueReference(ref));
273
- }
274
- serialize() {
275
- return {
276
- type: this.type,
277
- functionName: this.functionName,
278
- arguments: this.arguments.map((arg) => arg?.serialize()),
279
- editorConfig: this.editorConfig
280
- };
281
- }
282
- };
283
- //#endregion
284
- //#region src/expressions/template-value.ts
285
- let TemplateDefTypes = /* @__PURE__ */ function(TemplateDefTypes) {
286
- TemplateDefTypes["Default"] = "default";
287
- TemplateDefTypes["Date2String"] = "date2string";
288
- return TemplateDefTypes;
289
- }({});
290
- const serializeTemplateValue = (templateValue) => {
291
- const json = {
292
- type: templateValue.type,
293
- returnType: templateValue.returnType,
294
- expression: templateValue.expression?.serialize()
295
- };
296
- if (templateValue.type === "date2string") json.dateFormat = templateValue.dateFormat;
297
- return json;
298
- };
299
- const serializeTemplateValues = (templateValues) => {
300
- const json = {};
301
- for (const [key, value] of templateValues.entries()) json[key] = serializeTemplateValue(value);
302
- return json;
303
- };
304
- const deserializeTemplateValue = (json) => {
305
- return {
306
- type: json.type,
307
- expression: json.expression ? Expression.deserialize(json.expression) : void 0,
308
- returnType: json.returnType,
309
- dateFormat: json.dateFormat
310
- };
311
- };
312
- const deserializeTemplateValues = (json) => {
313
- return new Map(Object.entries(json).map(([key, value]) => [key, deserializeTemplateValue(value)]));
314
- };
315
- //#endregion
316
- //#region src/survey/responses/value-types.ts
317
- const ValueType = {
318
- string: "string",
319
- duration: "duration",
320
- reference: "reference",
321
- number: "number",
322
- boolean: "boolean",
323
- date: "date",
324
- stringArray: "string[]",
325
- durationArray: "duration[]",
326
- referenceArray: "reference[]",
327
- numberArray: "number[]",
328
- dateArray: "date[]"
329
- };
330
- const DurationUnits = {
331
- seconds: "seconds",
332
- minutes: "minutes",
333
- hours: "hours",
334
- days: "days",
335
- weeks: "weeks",
336
- months: "months",
337
- years: "years"
338
- };
339
- const NumberPrecision = {
340
- int: "int",
341
- float: "float"
342
- };
343
- function isPlainObject(value) {
344
- return value !== null && typeof value === "object" && !Array.isArray(value);
345
- }
346
- function isFiniteNumber(value) {
347
- return typeof value === "number" && Number.isFinite(value);
348
- }
349
- function isStringArray(value) {
350
- return Array.isArray(value) && value.every((item) => typeof item === "string");
351
- }
352
- function isNumberArray(value) {
353
- return Array.isArray(value) && value.every((item) => isFiniteNumber(item));
354
- }
355
- function isOptionalNumberPrecision(value) {
356
- return value === void 0 || value === NumberPrecision.int || value === NumberPrecision.float;
357
- }
358
- function isDurationUnit(value) {
359
- return typeof value === "string" && Object.values(DurationUnits).includes(value);
360
- }
361
- function isResponseValue(value) {
362
- if (!isPlainObject(value) || typeof value.type !== "string") return false;
363
- switch (value.type) {
364
- case ValueType.string:
365
- case ValueType.reference: return typeof value.value === "string";
366
- case ValueType.number: return isFiniteNumber(value.value) && isOptionalNumberPrecision(value.precision);
367
- case ValueType.boolean: return typeof value.value === "boolean";
368
- case ValueType.date: return isFiniteNumber(value.value);
369
- case ValueType.duration: return isFiniteNumber(value.value) && isDurationUnit(value.unit) && isOptionalNumberPrecision(value.precision);
370
- case ValueType.stringArray:
371
- case ValueType.referenceArray: return isStringArray(value.value);
372
- case ValueType.numberArray:
373
- case ValueType.dateArray: return isNumberArray(value.value) && (value.type !== ValueType.numberArray || isOptionalNumberPrecision(value.precision));
374
- case ValueType.durationArray: return isNumberArray(value.value) && isDurationUnit(value.unit) && isOptionalNumberPrecision(value.precision);
375
- default: return false;
376
- }
377
- }
378
- function assertResponseValue(value, path) {
379
- if (!isResponseValue(value)) throw new Error(`Invalid response value at '${path}'`);
380
- }
381
- //#endregion
382
- //#region src/survey/items/utils.ts
383
- const displayConditionsFromJson = (json) => {
384
- return {
385
- root: json.root ? Expression.deserialize(json.root) : void 0,
386
- components: json.components ? Object.fromEntries(Object.entries(json.components).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0
387
- };
388
- };
389
- const disabledConditionsFromJson = (json) => {
390
- return { components: json.components ? Object.fromEntries(Object.entries(json.components).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0 };
391
- };
392
- //#endregion
393
- //#region src/survey/items/prefill.ts
394
- const SurveyItemPrefillApplyMode = { ifEmpty: "ifEmpty" };
395
- const SurveyItemPrefillTargetType = {
396
- itemResponse: "itemResponse",
397
- field: "field",
398
- embeddedField: "embeddedField"
399
- };
400
- function deserializeSurveyItemPrefill(json) {
401
- switch (json.source.type) {
402
- case "static": return {
403
- id: json.id,
404
- target: json.target,
405
- when: Expression.deserialize(json.when),
406
- apply: json.apply,
407
- source: {
408
- type: "static",
409
- value: json.source.value
410
- }
411
- };
412
- case "expression": return {
413
- id: json.id,
414
- target: json.target,
415
- when: Expression.deserialize(json.when),
416
- apply: json.apply,
417
- source: {
418
- type: "expression",
419
- expression: Expression.deserialize(json.source.expression) ?? (() => {
420
- throw new Error(`Prefill '${json.id}' is missing a valid expression source`);
421
- })()
422
- }
423
- };
424
- case "templateValue": return {
425
- id: json.id,
426
- target: json.target,
427
- when: Expression.deserialize(json.when),
428
- apply: json.apply,
429
- source: {
430
- type: "templateValue",
431
- key: json.source.key
432
- }
433
- };
434
- case "previousResponse": return {
435
- id: json.id,
436
- target: json.target,
437
- when: Expression.deserialize(json.when),
438
- apply: json.apply,
439
- source: {
440
- type: "previousResponse",
441
- ref: json.source.ref,
442
- ifUnsupported: json.source.ifUnsupported
443
- }
444
- };
445
- default: throw new Error(`Unsupported prefill source type: ${json.source.type}`);
446
- }
447
- }
448
- function serializeSurveyItemPrefill(prefill) {
449
- switch (prefill.source.type) {
450
- case "static": return {
451
- id: prefill.id,
452
- target: prefill.target,
453
- when: prefill.when?.serialize(),
454
- apply: prefill.apply,
455
- source: {
456
- type: "static",
457
- value: prefill.source.value
458
- }
459
- };
460
- case "expression": return {
461
- id: prefill.id,
462
- target: prefill.target,
463
- when: prefill.when?.serialize(),
464
- apply: prefill.apply,
465
- source: {
466
- type: "expression",
467
- expression: prefill.source.expression.serialize() ?? (() => {
468
- throw new Error(`Prefill '${prefill.id}' cannot serialize an empty expression source`);
469
- })()
470
- }
471
- };
472
- case "templateValue": return {
473
- id: prefill.id,
474
- target: prefill.target,
475
- when: prefill.when?.serialize(),
476
- apply: prefill.apply,
477
- source: {
478
- type: "templateValue",
479
- key: prefill.source.key
480
- }
481
- };
482
- case "previousResponse": return {
483
- id: prefill.id,
484
- target: prefill.target,
485
- when: prefill.when?.serialize(),
486
- apply: prefill.apply,
487
- source: {
488
- type: "previousResponse",
489
- ref: prefill.source.ref,
490
- ifUnsupported: prefill.source.ifUnsupported
491
- }
492
- };
493
- default: throw new Error(`Unsupported prefill source type: ${prefill.source.type}`);
494
- }
495
- }
496
- function prefillTargetsEqual(left, right) {
497
- if (left.type !== right.type) return false;
498
- switch (left.type) {
499
- case SurveyItemPrefillTargetType.itemResponse: return true;
500
- case SurveyItemPrefillTargetType.field: return left.fieldId === right.fieldId;
501
- case SurveyItemPrefillTargetType.embeddedField: return left.optionId === right.optionId && left.fieldId === right.fieldId;
502
- default: return false;
503
- }
504
- }
505
- //#endregion
506
- //#region src/survey/items/survey-item.ts
507
- /**
508
- * Base class for survey item handlers.
509
- * Each handler holds type, id, key, and config (properly typed) and implements core methods.
510
- * Subclass for each item type; the core works with SurveyItemHandler only—no casting needed.
511
- */
512
- var SurveyItemCore = class {
513
- id;
514
- key;
515
- config;
516
- _rawItem;
517
- displayConditions;
518
- disabledConditions;
519
- validations;
520
- prefills;
521
- constructor(rawItem) {
522
- this._rawItem = rawItem;
523
- this.updateExpressions();
524
- this.id = this._rawItem.id;
525
- this.key = this._rawItem.key;
526
- this.config = this.parseConfig(this._rawItem.config);
527
- }
528
- getAdditionalResponseValueSlots() {
529
- return {};
530
- }
531
- getAvailableResponseValueSlots(byType) {
532
- const valueRefs = {};
533
- for (const slot of this.getResponseSlotDefinitions()) {
534
- valueRefs[`${this.id}...get...${slot.slotId}`] = slot.valueType;
535
- valueRefs[`${this.id}...isDefined...${slot.slotId}`] = ValueType.boolean;
536
- }
537
- Object.assign(valueRefs, this.getAdditionalResponseValueSlots());
538
- if (!byType) return valueRefs;
539
- return Object.fromEntries(Object.entries(valueRefs).filter(([, valueType]) => valueType === byType));
540
- }
541
- get rawItem() {
542
- return this._rawItem;
543
- }
544
- get metadata() {
545
- return this._rawItem.metadata ?? {};
546
- }
547
- set metadata(metadata) {
548
- this.updateRawItem({
549
- ...this._rawItem,
550
- metadata
551
- });
552
- }
553
- get editorMetadata() {
554
- return this._rawItem.editorMetadata ?? {};
555
- }
556
- setCreatedAt(timestamp) {
557
- this.updateRawItem({
558
- ...this._rawItem,
559
- editorMetadata: {
560
- ...this._rawItem.editorMetadata,
561
- createdAt: timestamp
562
- }
563
- });
564
- }
565
- setModifiedAt(timestamp) {
566
- this.updateRawItem({
567
- ...this._rawItem,
568
- editorMetadata: {
569
- ...this._rawItem.editorMetadata,
570
- createdAt: this._rawItem.editorMetadata?.createdAt ?? timestamp,
571
- updatedAt: timestamp
572
- }
573
- });
574
- }
575
- updateRawItem(rawItem) {
576
- if (rawItem.id !== this.id) throw new Error("Cannot update raw item with a different id");
577
- this._rawItem = rawItem;
578
- this.updateExpressions();
579
- this.key = rawItem.key;
580
- this.config = this.parseConfig(rawItem.config);
581
- }
582
- updateExpressions() {
583
- this.displayConditions = this._rawItem.displayConditions ? displayConditionsFromJson(this._rawItem.displayConditions) : void 0;
584
- this.disabledConditions = this._rawItem.disabledConditions ? disabledConditionsFromJson(this._rawItem.disabledConditions) : void 0;
585
- this.validations = this._rawItem.validations ? Object.fromEntries(Object.entries(this._rawItem.validations).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0;
586
- this.prefills = this._rawItem.prefills?.map((prefill) => deserializeSurveyItemPrefill(prefill));
587
- }
588
- getReferenceUsages() {
589
- const usages = [];
590
- if (this.displayConditions) {
591
- for (const ref of this.displayConditions.root?.responseVariableRefs || []) usages.push({
592
- itemId: this.id,
593
- usageType: "displayConditions",
594
- valueReference: ref
595
- });
596
- for (const [componentKey, expression] of Object.entries(this.displayConditions.components || {})) for (const ref of expression?.responseVariableRefs || []) usages.push({
597
- itemId: this.id,
598
- fullComponentKey: componentKey,
599
- usageType: "displayConditions",
600
- valueReference: ref
601
- });
602
- }
603
- if (this.disabledConditions) for (const [componentKey, expression] of Object.entries(this.disabledConditions.components || {})) for (const ref of expression?.responseVariableRefs || []) usages.push({
604
- itemId: this.id,
605
- fullComponentKey: componentKey,
606
- usageType: "disabledConditions",
607
- valueReference: ref
608
- });
609
- if (this.validations) for (const [validationKey, expression] of Object.entries(this.validations)) for (const ref of expression?.responseVariableRefs || []) usages.push({
610
- itemId: this.id,
611
- fullComponentKey: validationKey,
612
- usageType: "validations",
613
- valueReference: ref
614
- });
615
- if (this.prefills) for (const prefill of this.prefills) {
616
- for (const ref of prefill.when?.responseVariableRefs || []) usages.push({
617
- itemId: this.id,
618
- fullComponentKey: prefill.id,
619
- usageType: "prefills",
620
- valueReference: ref
621
- });
622
- if (prefill.source.type === "expression") for (const ref of prefill.source.expression.responseVariableRefs || []) usages.push({
623
- itemId: this.id,
624
- fullComponentKey: prefill.id,
625
- usageType: "prefills",
626
- valueReference: ref
627
- });
628
- }
629
- return usages;
630
- }
631
- resolvePrefillTarget(target) {
632
- const slotDefinitions = this.getResponseSlotDefinitions();
633
- const explicitMatch = slotDefinitions.find((slot) => slot.prefillTarget !== void 0 && prefillTargetsEqual(slot.prefillTarget, target));
634
- if (explicitMatch) return explicitMatch;
635
- if (target.type === SurveyItemPrefillTargetType.itemResponse && slotDefinitions.length === 1) return slotDefinitions[0];
636
- }
637
- normalizePrefillValue(_prefill, targetSlot, value) {
638
- return value.type === targetSlot.valueType ? value : void 0;
639
- }
640
- };
641
- //#endregion
642
- //#region src/survey/items/types.ts
643
- let ReservedSurveyItemTypes = /* @__PURE__ */ function(ReservedSurveyItemTypes) {
644
- ReservedSurveyItemTypes["Group"] = "group";
645
- ReservedSurveyItemTypes["PageBreak"] = "page-break";
646
- return ReservedSurveyItemTypes;
647
- }({});
648
- //#endregion
649
- //#region src/survey/item-key.ts
650
- /**
651
- * SurveyItemKey stores the key of the item and the path to the item.
652
- */
653
- var SurveyItemKey = class {
654
- _itemKey;
655
- _path;
656
- _keySeparator;
657
- constructor(itemKey, path, keySeparator = ".") {
658
- this._itemKey = itemKey;
659
- this._path = path ?? [];
660
- this._keySeparator = keySeparator;
661
- }
662
- get itemKey() {
663
- return this._itemKey;
664
- }
665
- get path() {
666
- return this._path;
667
- }
668
- get parentFullKey() {
669
- return this._path.join(this._keySeparator);
670
- }
671
- get fullKey() {
672
- return [...this._path, this._itemKey].join(this._keySeparator);
673
- }
674
- get isRoot() {
675
- return this._path.length === 0;
676
- }
677
- get keySeparator() {
678
- return this._keySeparator;
679
- }
680
- set keySeparator(keySeparator) {
681
- this._keySeparator = keySeparator;
682
- }
683
- };
684
- //#endregion
685
- //#region src/survey/utils/group-utils.ts
686
- /**
687
- * Shared utilities for array operations.
688
- */
689
- /**
690
- * Swap two elements in an array by index.
691
- * @returns A new array with the elements swapped
692
- */
693
- function swapArrayElements(arr, fromIndex, toIndex) {
694
- const result = [...arr];
695
- [result[fromIndex], result[toIndex]] = [result[toIndex], result[fromIndex]];
696
- return result;
697
- }
698
- /**
699
- * Move an element from one index to another.
700
- * @returns A new array with the element moved
701
- */
702
- function moveArrayElement(arr, fromIndex, toIndex) {
703
- const result = [...arr];
704
- const [moved] = result.splice(fromIndex, 1);
705
- result.splice(toIndex, 0, moved);
706
- return result;
707
- }
708
- //#endregion
709
- //#region src/survey/registry/built-in-items.ts
710
- var GroupItemCore = class extends SurveyItemCore {
711
- type = "group";
712
- parseConfig(rawConfig) {
713
- const cfg = rawConfig ?? {};
714
- return {
715
- items: cfg.items,
716
- shuffleItems: cfg.shuffleItems,
717
- isRoot: cfg.isRoot
718
- };
719
- }
720
- serializeConfig() {
721
- return this.config;
722
- }
723
- isInteractive() {
724
- return false;
725
- }
726
- getResponseSlotDefinitions() {
727
- return [];
728
- }
729
- isRoot() {
730
- return this.config.isRoot ?? false;
731
- }
732
- get items() {
733
- return this.config.items ?? [];
734
- }
735
- /**
736
- * Add a child item id to this group at the given index.
737
- * @param itemId The id of the item to add to this group.
738
- * @param index The index at which to add the item. If not provided, the item is added to the end of the group.
739
- */
740
- addChild(itemId, index) {
741
- const items = this.config.items ??= [];
742
- const insertIndex = index !== void 0 ? Math.min(index, items.length) : items.length;
743
- items.splice(insertIndex, 0, itemId);
744
- this.updateRawItem({
745
- ...this.rawItem,
746
- config: { ...this.config }
747
- });
748
- }
749
- /**
750
- * Remove a child item id from this group.
751
- * @param itemId The id of the item to remove from this group.
752
- */
753
- removeChild(itemId) {
754
- const items = this.config.items ??= [];
755
- const index = items.indexOf(itemId);
756
- if (index !== -1) {
757
- items.splice(index, 1);
758
- this.updateRawItem({
759
- ...this.rawItem,
760
- config: { ...this.config }
761
- });
762
- }
763
- }
764
- /**
765
- * Check if an item is a direct child of this group.
766
- */
767
- hasChild(itemId) {
768
- return this.items.includes(itemId);
769
- }
770
- /** Whether items in this group should be shuffled. */
771
- get shuffleItems() {
772
- return this.config.shuffleItems ?? false;
773
- }
774
- set shuffleItems(value) {
775
- this.config.shuffleItems = value;
776
- this.updateRawItem({
777
- ...this.rawItem,
778
- config: { ...this.config }
779
- });
780
- }
781
- /**
782
- * Get the ordered list of child item ids.
783
- */
784
- getChildrenIds() {
785
- return [...this.items];
786
- }
787
- /**
788
- * Swap two items by their positions in the group.
789
- * @throws Error if indices are out of bounds
790
- */
791
- swapItemsByIndex(from, to) {
792
- const items = this.items;
793
- if (from < 0 || from >= items.length || to < 0 || to >= items.length) throw new Error(`Index out of bounds. Valid range is 0-${items.length - 1}`);
794
- if (from === to) return;
795
- this.config.items = swapArrayElements(items, from, to);
796
- this.updateRawItem({
797
- ...this.rawItem,
798
- config: { ...this.config }
799
- });
800
- }
801
- /**
802
- * Swap two items by their ids.
803
- * @throws Error if either id is not found in this group
804
- */
805
- swapItemsById(id, withId) {
806
- const items = this.items;
807
- const index1 = items.indexOf(id);
808
- const index2 = items.indexOf(withId);
809
- if (index1 === -1) throw new Error(`Item '${id}' not found in group '${this.key}'`);
810
- if (index2 === -1) throw new Error(`Item '${withId}' not found in group '${this.key}'`);
811
- this.swapItemsByIndex(index1, index2);
812
- }
813
- /**
814
- * Move an item by id to a specific index.
815
- * @throws Error if the item is not found or index is out of bounds
816
- */
817
- moveItemToIndex(id, index) {
818
- const items = this.items;
819
- const fromIndex = items.indexOf(id);
820
- if (fromIndex === -1) throw new Error(`Item '${id}' not found in group '${this.key}'`);
821
- if (index < 0 || index >= items.length) throw new Error(`Index out of bounds. Valid range is 0-${items.length - 1}`);
822
- if (fromIndex === index) return;
823
- this.config.items = moveArrayElement(items, fromIndex, index);
824
- this.updateRawItem({
825
- ...this.rawItem,
826
- config: { ...this.config }
827
- });
828
- }
829
- };
830
- var PageBreakItemCore = class extends SurveyItemCore {
831
- type = "page-break";
832
- parseConfig(_rawConfig) {
833
- return {};
834
- }
835
- serializeConfig() {}
836
- isInteractive() {
837
- return false;
838
- }
839
- getResponseSlotDefinitions() {
840
- return [];
841
- }
842
- };
843
- const builtInItemCoreRegistry = {
844
- ["group"]: GroupItemCore,
845
- ["page-break"]: PageBreakItemCore
846
- };
847
- const isBuiltInItemType = (itemType) => itemType in builtInItemCoreRegistry;
848
- //#endregion
849
- //#region src/survey/registry/item-registry.ts
850
- /** Convert constructor registry to explicit definitions for higher-layer composition. */
851
- function toItemTypeDefinitionRegistry(registry) {
852
- return Object.fromEntries(Object.entries(registry).map(([itemType, constructor]) => [itemType, {
853
- itemType,
854
- constructor
855
- }]));
856
- }
857
- /**
858
- * Creates a handler instance for a raw survey item.
859
- * Uses built-in registry for group/page-break, otherwise the provided plugin registry.
860
- */
861
- function createItemCore(rawItem, pluginRegistry) {
862
- if (isBuiltInItemType(rawItem.itemType)) {
863
- const HandlerClass = builtInItemCoreRegistry[rawItem.itemType];
864
- return new HandlerClass(rawItem);
865
- }
866
- if (pluginRegistry && rawItem.itemType in pluginRegistry) {
867
- const HandlerClass = pluginRegistry[rawItem.itemType];
868
- return new HandlerClass(rawItem);
869
- }
870
- throw new Error(`Unknown item type: ${rawItem.itemType}`);
871
- }
872
- /**
873
- * Merge built-in handlers with plugin registry for a complete registry.
874
- */
875
- function createFullRegistry(pluginRegistry) {
876
- return {
877
- ...builtInItemCoreRegistry,
878
- ...pluginRegistry
879
- };
880
- }
881
- /** Build full registry as explicit definitions for editor/ui composition. */
882
- function createItemTypeDefinitionRegistry(pluginRegistry) {
883
- return toItemTypeDefinitionRegistry(createFullRegistry(pluginRegistry));
884
- }
885
- //#endregion
886
- //#region src/survey/survey-file-schema.ts
887
- const CURRENT_SURVEY_SCHEMA = "https://github.com/case-framework/case-survey-toolkit/packages/survey-core/schemas/survey-schema.json";
888
- //#endregion
889
- //#region src/survey/utils/translations.ts
890
- const validateLocale = (locale) => {
891
- if (locale.trim() === "") throw new Error("Locale cannot be empty");
892
- };
893
- var SurveyItemTranslations = class SurveyItemTranslations {
894
- _translations;
895
- constructor() {
896
- this._translations = {};
897
- }
898
- setContent(locale, contentKey, content) {
899
- validateLocale(locale);
900
- if (!this._translations?.[locale]) {
901
- if (!content) return;
902
- this._translations[locale] = {};
903
- }
904
- if (!content) delete this._translations[locale][contentKey];
905
- else this._translations[locale][contentKey] = content;
906
- }
907
- setAllForLocale(locale, content) {
908
- validateLocale(locale);
909
- if (!this._translations?.[locale]) {
910
- if (!content) return;
911
- this._translations[locale] = {};
912
- }
913
- this._translations[locale] = content || {};
914
- }
915
- get locales() {
916
- return Object.keys(this._translations || {});
917
- }
918
- getAllForLocale(locale) {
919
- return this._translations?.[locale];
920
- }
921
- getContent(locale, contentKey, fallbackLocale) {
922
- const content = this._translations?.[locale]?.[contentKey];
923
- if (content) return content;
924
- if (fallbackLocale) return this._translations?.[fallbackLocale]?.[contentKey];
925
- }
926
- /**
927
- * Create a deep clone that can be safely mutated before persisting with
928
- * `setItemTranslations` or `updateItemTranslations`.
929
- */
930
- clone() {
931
- const cloned = new SurveyItemTranslations();
932
- for (const locale of this.locales) {
933
- const localeContent = this.getAllForLocale(locale);
934
- if (!localeContent) continue;
935
- cloned.setAllForLocale(locale, structuredCloneMethod(localeContent));
936
- }
937
- return cloned;
938
- }
939
- /**
940
- * Remove a single content key from every locale in this translation set.
941
- */
942
- removeContentKey(contentKey) {
943
- for (const locale of this.locales) delete this._translations?.[locale]?.[contentKey];
944
- }
945
- /**
946
- * Remove every content key that is exactly the prefix or nested beneath it.
947
- */
948
- removeContentKeysWithPrefix(prefix) {
949
- for (const locale of this.locales) {
950
- const localeContent = this._translations?.[locale];
951
- if (!localeContent) continue;
952
- for (const contentKey of Object.keys(localeContent)) if (contentKey === prefix || contentKey.startsWith(prefix + ".")) delete localeContent[contentKey];
953
- }
954
- }
955
- /**
956
- * Remove empty locales in place.
957
- * Returns `undefined` when no translations remain so callers can pass the
958
- * result directly into `setItemTranslations` / `updateItemTranslations`.
959
- */
960
- compact() {
961
- for (const locale of this.locales) {
962
- const localeContent = this._translations?.[locale];
963
- if (!localeContent) continue;
964
- if (Object.keys(localeContent).length === 0) delete this._translations?.[locale];
965
- }
966
- return this.locales.length > 0 ? this : void 0;
967
- }
968
- };
969
- var SurveyTranslations = class {
970
- _translations;
971
- constructor(translations) {
972
- this._translations = translations || {};
973
- }
974
- serialize() {
975
- if (this.locales.length === 0) return;
976
- return this._translations;
977
- }
978
- get locales() {
979
- return Object.keys(this._translations);
980
- }
981
- /**
982
- * Register a locale even when it has no translated content yet.
983
- * Returns false when the locale is already present.
984
- */
985
- addLocale(locale) {
986
- validateLocale(locale);
987
- if (this._translations[locale]) return false;
988
- this._translations[locale] = {};
989
- return true;
990
- }
991
- removeLocale(locale) {
992
- validateLocale(locale);
993
- delete this._translations[locale];
994
- }
995
- renameLocale(oldLocale, newLocale) {
996
- validateLocale(oldLocale);
997
- validateLocale(newLocale);
998
- if (this._translations[oldLocale]) {
999
- this._translations[newLocale] = this._translations[oldLocale];
1000
- delete this._translations[oldLocale];
1001
- }
1002
- }
1003
- cloneLocaleAs(locale, newLocale) {
1004
- validateLocale(locale);
1005
- validateLocale(newLocale);
1006
- if (this._translations[locale]) this._translations[newLocale] = structuredCloneMethod(this._translations[locale]);
1007
- }
1008
- get surveyCardContent() {
1009
- const translations = {};
1010
- for (const locale of this.locales) {
1011
- const contentForLocale = this._translations?.[locale]?.surveyCardContent;
1012
- if (contentForLocale) translations[locale] = contentForLocale;
1013
- }
1014
- return translations;
1015
- }
1016
- get navigationContent() {
1017
- const translations = {};
1018
- for (const locale of this.locales) {
1019
- const contentForLocale = this._translations?.[locale]?.navigationContent;
1020
- if (contentForLocale) translations[locale] = contentForLocale;
1021
- }
1022
- return translations;
1023
- }
1024
- get validationMessages() {
1025
- const translations = {};
1026
- for (const locale of this.locales) {
1027
- const contentForLocale = this._translations?.[locale]?.validationMessages;
1028
- if (contentForLocale) translations[locale] = contentForLocale;
1029
- }
1030
- return translations;
1031
- }
1032
- setSurveyCardContent(locale, content) {
1033
- validateLocale(locale);
1034
- if (!this._translations[locale]) {
1035
- if (!content) return;
1036
- this._translations[locale] = {};
1037
- }
1038
- this._translations[locale].surveyCardContent = content;
1039
- }
1040
- setNavigationContent(locale, content) {
1041
- validateLocale(locale);
1042
- if (!this._translations[locale]) {
1043
- if (!content) return;
1044
- this._translations[locale] = {};
1045
- }
1046
- this._translations[locale].navigationContent = content;
1047
- }
1048
- setValidationMessages(locale, content) {
1049
- validateLocale(locale);
1050
- if (!this._translations[locale]) {
1051
- if (!content) return;
1052
- this._translations[locale] = {};
1053
- }
1054
- this._translations[locale].validationMessages = content;
1055
- }
1056
- getItemTranslations(itemId) {
1057
- const itemTranslations = new SurveyItemTranslations();
1058
- for (const locale of this.locales) {
1059
- const contentForLocale = this._translations?.[locale].itemTranslations?.[itemId];
1060
- itemTranslations.setAllForLocale(locale, contentForLocale);
1061
- }
1062
- return itemTranslations.locales.length > 0 ? itemTranslations : void 0;
1063
- }
1064
- /**
1065
- * Persist item translations after normalizing them:
1066
- * empty keys/locales are removed automatically, and an empty translation set
1067
- * clears the item's stored translations entirely.
1068
- */
1069
- setItemTranslations(itemId, itemContent) {
1070
- const normalizedItemContent = itemContent?.clone().compact();
1071
- normalizedItemContent?.locales.forEach((locale) => validateLocale(locale));
1072
- if (!normalizedItemContent) {
1073
- for (const locale of this.locales) if (this._translations[locale].itemTranslations?.[itemId]) delete this._translations[locale].itemTranslations?.[itemId];
1074
- } else {
1075
- const localesInUpdate = normalizedItemContent.locales;
1076
- for (const locale of localesInUpdate) if (!this.locales.includes(locale)) this._translations[locale] = {};
1077
- for (const locale of this.locales) if (localesInUpdate.includes(locale)) {
1078
- if (!this._translations[locale]) this._translations[locale] = {};
1079
- if (!this._translations[locale].itemTranslations) this._translations[locale].itemTranslations = {};
1080
- this._translations[locale].itemTranslations[itemId] = normalizedItemContent.getAllForLocale(locale);
1081
- } else delete this._translations[locale].itemTranslations?.[itemId];
1082
- }
1083
- }
1084
- /**
1085
- * Rename a component key (within an item) - update key in all translations and remove old key
1086
- * @param itemKey - The key of the item
1087
- * @param oldKey - The old key of the component
1088
- * @param newKey - The new key of the component
1089
- */
1090
- onComponentKeyChanged(itemKey, oldKey, newKey) {
1091
- for (const locale of this.locales) {
1092
- const itemTranslations = this._translations?.[locale].itemTranslations?.[itemKey];
1093
- if (itemTranslations) {
1094
- for (const key of Object.keys(itemTranslations)) if (key.startsWith(oldKey + ".") || key === oldKey) {
1095
- itemTranslations[key.replace(oldKey, newKey)] = { ...itemTranslations[key] };
1096
- delete itemTranslations[key];
1097
- }
1098
- }
1099
- }
1100
- }
1101
- /**
1102
- * Remove all translations for a component
1103
- * @param itemId - The id of the item
1104
- * @param componentKey - The key of the component
1105
- */
1106
- onComponentDeleted(itemId, componentKey) {
1107
- for (const locale of this.locales) {
1108
- const itemTranslations = this._translations?.[locale].itemTranslations?.[itemId];
1109
- if (itemTranslations) {
1110
- for (const key of Object.keys(itemTranslations)) if (key.startsWith(componentKey + ".") || key === componentKey) delete itemTranslations[key];
1111
- }
1112
- }
1113
- }
1114
- /**
1115
- * Remove all translations for an item
1116
- * @param id - The id of the item
1117
- */
1118
- onItemDeleted(id) {
1119
- for (const locale of this.locales) delete this._translations[locale].itemTranslations?.[id];
1120
- }
1121
- };
1122
- //#endregion
1123
- //#region src/survey/utils/content.ts
1124
- let ContentType = /* @__PURE__ */ function(ContentType) {
1125
- ContentType["richText"] = "richText";
1126
- ContentType["plain"] = "plain";
1127
- ContentType["md"] = "md";
1128
- return ContentType;
1129
- }({});
1130
- function hasRenderableRichTextBlock(block) {
1131
- switch (block.type) {
1132
- case "paragraph": return block.children.length > 0;
1133
- case "heading": return block.children.length > 0;
1134
- case "bulletList": return block.items.some((item) => item.children.some((paragraph) => paragraph.children.length > 0));
1135
- case "image": return true;
1136
- case "infoBox": return true;
1137
- case "separator": return true;
1138
- case "blockExtension": return true;
1139
- }
1140
- }
1141
- function hasRenderableRichTextContent(content) {
1142
- return content.doc.blocks.some((block) => hasRenderableRichTextBlock(block));
1143
- }
1144
- function createRichTextContent(text = "") {
1145
- return {
1146
- type: "richText",
1147
- version: 1,
1148
- doc: {
1149
- type: "doc",
1150
- blocks: [{
1151
- type: "paragraph",
1152
- children: text.length > 0 ? text.split("\n").flatMap((line, index) => {
1153
- const parts = [];
1154
- if (index > 0) parts.push({ type: "lineBreak" });
1155
- if (line.length > 0) parts.push({
1156
- type: "text",
1157
- text: line
1158
- });
1159
- return parts;
1160
- }) : []
1161
- }]
1162
- }
1163
- };
1164
- }
1165
- function getPlainTextFromLinkChildren(children) {
1166
- return children.map((inline) => {
1167
- switch (inline.type) {
1168
- case "text": return inline.text;
1169
- case "template": return `{${inline.key}}`;
1170
- case "lineBreak": return "\n";
1171
- }
1172
- }).join("");
1173
- }
1174
- function getPlainTextFromInlineNodes(inlines) {
1175
- return inlines.map((inline) => {
1176
- switch (inline.type) {
1177
- case "text": return inline.text;
1178
- case "template": return `{${inline.key}}`;
1179
- case "link": return getPlainTextFromLinkChildren(inline.children);
1180
- case "lineBreak": return "\n";
1181
- case "inlineExtension": return "";
1182
- }
1183
- }).join("");
1184
- }
1185
- function getPlainTextFromParagraphs(paragraphs) {
1186
- return paragraphs.map((paragraph) => getPlainTextFromInlineNodes(paragraph.children)).join("\n");
1187
- }
1188
- function getPlainTextFromBlock(block) {
1189
- switch (block.type) {
1190
- case "paragraph": return getPlainTextFromInlineNodes(block.children);
1191
- case "heading": return getPlainTextFromInlineNodes(block.children);
1192
- case "bulletList": return block.items.map((item) => getPlainTextFromParagraphs(item.children)).join("\n");
1193
- case "image": {
1194
- const captionText = block.caption ? getPlainTextFromParagraphs(block.caption) : "";
1195
- return [block.alt ?? "", captionText].filter(Boolean).join("\n");
1196
- }
1197
- case "infoBox": return [block.title ? getPlainTextFromInlineNodes(block.title) : "", getPlainTextFromParagraphs(block.children)].filter(Boolean).join("\n");
1198
- case "separator": return "";
1199
- case "blockExtension": return "";
1200
- }
1201
- }
1202
- function getPlainTextFromRichTextContent(content) {
1203
- return content.doc.blocks.map((block) => getPlainTextFromBlock(block)).filter((text) => text.length > 0).join("\n").trim();
1204
- }
1205
- function getContentPlainText(content) {
1206
- if (!content) return "";
1207
- if (content.type !== "richText") return content.content ?? "";
1208
- return getPlainTextFromRichTextContent(content);
1209
- }
1210
- function isContentEmpty(content) {
1211
- if (!content) return true;
1212
- if (content.type !== "richText") return content.content.trim().length === 0;
1213
- return !hasRenderableRichTextContent(content);
1214
- }
1215
- function getAssetUsagesFromContent(content) {
1216
- if (!content || content.type !== "richText") return [];
1217
- const usages = [];
1218
- content.doc.blocks.forEach((block, blockIndex) => {
1219
- if (block.type === "image" && block.source.type === "asset") usages.push({
1220
- assetId: block.source.assetId,
1221
- blockIndex
1222
- });
1223
- });
1224
- return usages;
1225
- }
1226
- //#endregion
1227
- //#region src/survey/survey.ts
1228
- var Survey = class Survey {
1229
- maxItemsPerPage;
1230
- metadata;
1231
- surveyItems = /* @__PURE__ */ new Map();
1232
- _templateValues = /* @__PURE__ */ new Map();
1233
- _assets = /* @__PURE__ */ new Map();
1234
- _translations;
1235
- constructor(pluginRegistry) {
1236
- this.pluginRegistry = pluginRegistry;
1237
- this._translations = new SurveyTranslations();
1238
- }
1239
- /** Plugin registry used when parsing items. Exposed for editors that re-parse without their own registry. */
1240
- getPluginRegistry() {
1241
- return this.pluginRegistry;
1242
- }
1243
- /**
1244
- * Create a survey item from raw JSON data.
1245
- * Uses the survey's plugin registry when available.
1246
- */
1247
- createItemFromRaw(rawItem) {
1248
- return createItemCore(rawItem, this.pluginRegistry);
1249
- }
1250
- /**
1251
- * Create a minimal blank survey with a root group and one empty page.
1252
- */
1253
- static createBlankSurvey(pluginRegistry, surveyKey) {
1254
- const newKey = surveyKey ?? "survey";
1255
- const rawSurvey = {
1256
- $schema: CURRENT_SURVEY_SCHEMA,
1257
- surveyItems: [{
1258
- id: generateId(),
1259
- key: newKey,
1260
- itemType: "group",
1261
- config: {
1262
- isRoot: true,
1263
- items: [],
1264
- shuffleItems: false
1265
- }
1266
- }]
1267
- };
1268
- return Survey.fromJson(rawSurvey, pluginRegistry);
1269
- }
1270
- static fromJson(json, pluginRegistry) {
1271
- const survey = new Survey(pluginRegistry);
1272
- const rawSurvey = json;
1273
- if (rawSurvey.$schema !== "https://github.com/case-framework/case-survey-toolkit/packages/survey-core/schemas/survey-schema.json") throw new Error(`Unsupported survey schema: ${rawSurvey.$schema}`);
1274
- survey.surveyItems = /* @__PURE__ */ new Map();
1275
- if (!rawSurvey.surveyItems || rawSurvey.surveyItems.length === 0) throw new Error("surveyItems is required");
1276
- rawSurvey.surveyItems.forEach((item) => {
1277
- const surveyItem = createItemCore(item, pluginRegistry);
1278
- survey.surveyItems.set(surveyItem.id, surveyItem);
1279
- });
1280
- if (rawSurvey.templateValues) survey._templateValues = deserializeTemplateValues(rawSurvey.templateValues);
1281
- if (rawSurvey.assets) survey._assets = new Map(Object.entries(rawSurvey.assets));
1282
- survey._translations = new SurveyTranslations(rawSurvey.translations);
1283
- if (rawSurvey.maxItemsPerPage) survey.maxItemsPerPage = rawSurvey.maxItemsPerPage;
1284
- if (rawSurvey.metadata) survey.metadata = rawSurvey.metadata;
1285
- return survey;
1286
- }
1287
- serialize() {
1288
- const json = {
1289
- $schema: CURRENT_SURVEY_SCHEMA,
1290
- surveyItems: Array.from(this.surveyItems.values()).map((item) => item.rawItem)
1291
- };
1292
- json.translations = this._translations?.serialize();
1293
- if (this._templateValues) json.templateValues = serializeTemplateValues(this._templateValues);
1294
- if (this._assets && this._assets.size > 0) json.assets = Object.fromEntries(this._assets.entries());
1295
- if (this.maxItemsPerPage) json.maxItemsPerPage = this.maxItemsPerPage;
1296
- if (this.metadata) json.metadata = this.metadata;
1297
- return json;
1298
- }
1299
- get surveyKey() {
1300
- if (!this.rootItem) return;
1301
- return this.rootItem.key;
1302
- }
1303
- get locales() {
1304
- return this._translations?.locales || [];
1305
- }
1306
- get rootItem() {
1307
- return Array.from(this.surveyItems.values()).find((item) => item instanceof GroupItemCore && item.isRoot);
1308
- }
1309
- get keyIndex() {
1310
- const index = [];
1311
- for (const item of this.surveyItems.values()) {
1312
- const path = this.getItemPath(item.id);
1313
- index.push({
1314
- itemId: item.id,
1315
- key: item.key,
1316
- keyPath: path.map((id) => this.surveyItems.get(id)?.key ?? ""),
1317
- path
1318
- });
1319
- }
1320
- return index;
1321
- }
1322
- getItemPath(itemId) {
1323
- const item = this.surveyItems.get(itemId);
1324
- if (!item) throw new Error(`Item ${itemId} not found`);
1325
- if (item instanceof GroupItemCore && item.isRoot()) return [];
1326
- const parentItem = this.getParentItem(itemId);
1327
- if (!parentItem) throw new Error(`Parent item for ${itemId} not found`);
1328
- return [...this.getItemPath(parentItem.id), parentItem.id];
1329
- }
1330
- getItemKey(itemId) {
1331
- const item = this.surveyItems.get(itemId);
1332
- if (!item) return;
1333
- const keyPath = this.getItemPath(itemId).map((id) => this.surveyItems.get(id)?.key ?? "");
1334
- return new SurveyItemKey(item.key, keyPath);
1335
- }
1336
- getParentItem(itemId) {
1337
- const item = this.surveyItems.get(itemId);
1338
- if (!item) throw new Error(`Item ${itemId} not found`);
1339
- if (item instanceof GroupItemCore && item.isRoot()) return;
1340
- const parentItem = Array.from(this.surveyItems.values()).find((item) => item instanceof GroupItemCore && item.config.items?.includes(itemId));
1341
- if (!parentItem) return;
1342
- return parentItem;
1343
- }
1344
- getChildrenItems(parentId) {
1345
- const parentItem = this.surveyItems.get(parentId);
1346
- if (!parentItem || !(parentItem instanceof GroupItemCore)) return [];
1347
- return parentItem.config.items?.map((id) => this.surveyItems.get(id))?.filter((item) => item !== void 0) || [];
1348
- }
1349
- getSiblings(itemId) {
1350
- if (!this.surveyItems.get(itemId)) return [];
1351
- return this.getParentItem(itemId)?.items?.map((id) => this.surveyItems.get(id))?.filter((item) => item !== void 0) || [];
1352
- }
1353
- get translations() {
1354
- if (!this._translations) this._translations = new SurveyTranslations();
1355
- return this._translations;
1356
- }
1357
- getItemTranslations(id) {
1358
- if (!this.surveyItems.get(id)) throw new Error(`Item ${id} not found`);
1359
- return this._translations?.getItemTranslations(id);
1360
- }
1361
- getTemplateValue(templateValueKey) {
1362
- return this._templateValues?.get(templateValueKey);
1363
- }
1364
- setTemplateValue(templateValueKey, templateValue) {
1365
- if (!this._templateValues) this._templateValues = /* @__PURE__ */ new Map();
1366
- this._templateValues?.set(templateValueKey, templateValue);
1367
- }
1368
- deleteTemplateValue(templateValueKey) {
1369
- this._templateValues?.delete(templateValueKey);
1370
- }
1371
- getTemplateValueKeys() {
1372
- return Array.from(this._templateValues?.keys() || []);
1373
- }
1374
- getAsset(assetId) {
1375
- const asset = this._assets?.get(assetId);
1376
- return asset ? structuredCloneMethod(asset) : void 0;
1377
- }
1378
- setAsset(assetId, asset) {
1379
- if (!this._assets) this._assets = /* @__PURE__ */ new Map();
1380
- this._assets.set(assetId, structuredCloneMethod(asset));
1381
- }
1382
- deleteAsset(assetId) {
1383
- this._assets?.delete(assetId);
1384
- }
1385
- hasAsset(assetId) {
1386
- return this._assets?.has(assetId) ?? false;
1387
- }
1388
- getAssetIds() {
1389
- return Array.from(this._assets?.keys() || []);
1390
- }
1391
- getAssets() {
1392
- return new Map(Array.from(this._assets?.entries() || []).map(([assetId, asset]) => [assetId, structuredCloneMethod(asset)]));
1393
- }
1394
- getAssetUsages(filterAssetId) {
1395
- const usages = [];
1396
- const pushContentUsages = (content, usageScope, contentKey, locale, itemId) => {
1397
- for (const usage of getAssetUsagesFromContent(content)) {
1398
- if (filterAssetId && usage.assetId !== filterAssetId) continue;
1399
- usages.push({
1400
- assetId: usage.assetId,
1401
- itemId,
1402
- locale,
1403
- contentKey,
1404
- usageScope,
1405
- blockIndex: usage.blockIndex
1406
- });
1407
- }
1408
- };
1409
- const serializedTranslations = this._translations?.serialize();
1410
- const rootItemId = this.rootItem?.id;
1411
- if (!serializedTranslations) return usages;
1412
- for (const [locale, localeTranslations] of Object.entries(serializedTranslations)) {
1413
- for (const [contentKey, content] of Object.entries(localeTranslations.surveyCardContent || {})) pushContentUsages(content, "surveyCard", contentKey, locale, rootItemId);
1414
- for (const [contentKey, content] of Object.entries(localeTranslations.navigationContent || {})) pushContentUsages(content, "navigation", contentKey, locale, rootItemId);
1415
- for (const [contentKey, content] of Object.entries(localeTranslations.validationMessages || {})) pushContentUsages(content, "validationMessages", contentKey, locale, rootItemId);
1416
- for (const [itemId, itemTranslations] of Object.entries(localeTranslations.itemTranslations || {})) for (const [contentKey, content] of Object.entries(itemTranslations || {})) pushContentUsages(content, "itemTranslation", contentKey, locale, itemId);
1417
- }
1418
- return usages;
1419
- }
1420
- getAvailableResponseValueSlots(byType) {
1421
- let valueRefs = {};
1422
- for (const item of this.surveyItems.values()) valueRefs = {
1423
- ...valueRefs,
1424
- ...item.getAvailableResponseValueSlots(byType)
1425
- };
1426
- return valueRefs;
1427
- }
1428
- getResponseSlotDefinitions() {
1429
- return Array.from(this.surveyItems.values()).map((item) => ({
1430
- itemId: item.id,
1431
- slots: item.getResponseSlotDefinitions()
1432
- }));
1433
- }
1434
- /**
1435
- * Get all reference usages for the survey
1436
- * @param forItemId - optional item id to filter usages for a specific item and its children (if not provided, all usages are returned)
1437
- * @returns all reference usages for the survey (or for a specific item and its children)
1438
- */
1439
- getReferenceUsages(forItemId) {
1440
- const usages = [];
1441
- for (const item of this.surveyItems.values()) {
1442
- if (forItemId && item.id !== forItemId && !this.isDescendantOf(item.id, forItemId)) continue;
1443
- usages.push(...item.getReferenceUsages());
1444
- }
1445
- if (this._templateValues) for (const [templateValueKey, templateValue] of this._templateValues.entries()) for (const ref of templateValue.expression?.responseVariableRefs || []) usages.push({
1446
- itemId: templateValueKey,
1447
- usageType: "templateValues",
1448
- valueReference: ref
1449
- });
1450
- return usages;
1451
- }
1452
- isDescendantOf(targetId, ancestorId) {
1453
- if (targetId === ancestorId) return true;
1454
- return this.getItemPath(targetId).includes(ancestorId);
1455
- }
1456
- };
1457
- //#endregion
1458
- export { generateId as $, DurationUnits as A, ConstExpression as B, ReservedSurveyItemTypes as C, deserializeSurveyItemPrefill as D, SurveyItemPrefillTargetType as E, TemplateDefTypes as F, FunctionExpression as G, ContextVariableType as H, deserializeTemplateValue as I, ReferenceUsageType as J, FunctionExpressionNames as K, deserializeTemplateValues as L, ValueType as M, assertResponseValue as N, prefillTargetsEqual as O, isResponseValue as P, generateCodingKey as Q, serializeTemplateValue as R, SurveyItemKey as S, SurveyItemPrefillApplyMode as T, Expression as U, ContextVariableExpression as V, ExpressionType as W, ValueReferenceMethod as X, ValueReference as Y, createSeededRandom as Z, toItemTypeDefinitionRegistry as _, getContentPlainText as a, builtInItemCoreRegistry as b, hasRenderableRichTextContent as c, SurveyTranslations as d, shuffleArray as et, validateLocale as f, createItemTypeDefinitionRegistry as g, createItemCore as h, getAssetUsagesFromContent as i, NumberPrecision as j, serializeSurveyItemPrefill as k, isContentEmpty as l, createFullRegistry as m, ContentType as n, structuredCloneMethod as nt, getPlainTextFromRichTextContent as o, CURRENT_SURVEY_SCHEMA as p, ResponseVariableExpression as q, createRichTextContent as r, hasRenderableRichTextBlock as s, Survey as t, shuffleIndices as tt, SurveyItemTranslations as u, GroupItemCore as v, SurveyItemCore as w, isBuiltInItemType as x, PageBreakItemCore as y, serializeTemplateValues as z };
1459
-
1460
- //# sourceMappingURL=survey-BKblV5rN.mjs.map