@case-framework/survey-core 0.1.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,931 @@
1
+ //#region src/utils.ts
2
+ /**
3
+ * Shuffles an array of indices using the Fisher-Yates shuffle algorithm
4
+ * @param length - The length of the array to create indices for
5
+ * @returns A shuffled array of indices from 0 to length-1
6
+ */
7
+ function shuffleIndices(length) {
8
+ const shuffledIndices = Array.from({ length }, (_, i) => i);
9
+ for (let i = shuffledIndices.length - 1; i > 0; i--) {
10
+ const j = Math.floor(Math.random() * (i + 1));
11
+ [shuffledIndices[i], shuffledIndices[j]] = [shuffledIndices[j], shuffledIndices[i]];
12
+ }
13
+ return shuffledIndices;
14
+ }
15
+ function structuredCloneMethod(obj) {
16
+ if (typeof structuredClone !== "undefined") return structuredClone(obj);
17
+ return JSON.parse(JSON.stringify(obj));
18
+ }
19
+ const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
20
+ function generateId() {
21
+ const length = 10;
22
+ const timestamp = Date.now();
23
+ let encodedTimestamp = "";
24
+ let t = timestamp;
25
+ const base = 62;
26
+ while (t > 0) {
27
+ encodedTimestamp = ALPHABET.charAt(t % base) + encodedTimestamp;
28
+ t = Math.floor(t / base);
29
+ }
30
+ let randomPart = "";
31
+ for (let i = 0; i < length; i++) randomPart += ALPHABET.charAt(Math.floor(Math.random() * base));
32
+ return randomPart + encodedTimestamp;
33
+ }
34
+
35
+ //#endregion
36
+ //#region src/survey/utils/value-reference.ts
37
+ const SEPARATOR = "...";
38
+ let ValueReferenceMethod = /* @__PURE__ */ function(ValueReferenceMethod) {
39
+ ValueReferenceMethod["get"] = "get";
40
+ ValueReferenceMethod["isDefined"] = "isDefined";
41
+ return ValueReferenceMethod;
42
+ }({});
43
+ var ValueReference = class ValueReference {
44
+ _itemId;
45
+ _name;
46
+ _slotId;
47
+ constructor(str) {
48
+ const parts = str.split(SEPARATOR);
49
+ if (parts.length !== 3) throw new Error("Invalid value reference: " + str);
50
+ this._itemId = parts[0];
51
+ this._name = parts[1];
52
+ this._slotId = parts[2];
53
+ }
54
+ get itemId() {
55
+ return this._itemId;
56
+ }
57
+ get name() {
58
+ return this._name;
59
+ }
60
+ get slotId() {
61
+ return this._slotId;
62
+ }
63
+ set itemId(itemId) {
64
+ this._itemId = itemId;
65
+ }
66
+ toString() {
67
+ return `${this._itemId}${SEPARATOR}${this._name}${this._slotId ? SEPARATOR + this._slotId : ""}`;
68
+ }
69
+ static fromParts(itemId, name, slotId) {
70
+ return new ValueReference(`${itemId}${SEPARATOR}${name}${SEPARATOR}${slotId}`);
71
+ }
72
+ };
73
+ let ReferenceUsageType = /* @__PURE__ */ function(ReferenceUsageType) {
74
+ ReferenceUsageType["displayConditions"] = "displayConditions";
75
+ ReferenceUsageType["templateValues"] = "templateValues";
76
+ ReferenceUsageType["validations"] = "validations";
77
+ ReferenceUsageType["disabledConditions"] = "disabledConditions";
78
+ return ReferenceUsageType;
79
+ }({});
80
+
81
+ //#endregion
82
+ //#region src/expressions/expression.ts
83
+ const ExpressionType = {
84
+ Const: "const",
85
+ ResponseVariable: "responseVariable",
86
+ ContextVariable: "contextVariable",
87
+ Function: "function"
88
+ };
89
+ const ContextVariableType = {
90
+ Locale: "locale",
91
+ ParticipantFlag: "participantFlag",
92
+ CustomValue: "customValue",
93
+ CustomExpression: "customExpression"
94
+ };
95
+ /**
96
+ * Base class for all expressions.
97
+ */
98
+ var Expression = class Expression {
99
+ type;
100
+ editorConfig;
101
+ constructor(type, editorConfig) {
102
+ this.type = type;
103
+ this.editorConfig = editorConfig;
104
+ }
105
+ static deserialize(json) {
106
+ if (!json) return;
107
+ switch (json.type) {
108
+ case ExpressionType.Const: return ConstExpression.deserialize(json);
109
+ case ExpressionType.ResponseVariable: return ResponseVariableExpression.deserialize(json);
110
+ case ExpressionType.ContextVariable: return ContextVariableExpression.deserialize(json);
111
+ case ExpressionType.Function: return FunctionExpression.deserialize(json);
112
+ }
113
+ }
114
+ clone() {
115
+ return Expression.deserialize(this.serialize()) ?? (() => {
116
+ throw new Error("Failed to clone expression");
117
+ })();
118
+ }
119
+ };
120
+ var ConstExpression = class ConstExpression extends Expression {
121
+ value;
122
+ constructor(value, editorConfig) {
123
+ super(ExpressionType.Const, editorConfig);
124
+ this.value = value;
125
+ this.type = ExpressionType.Const;
126
+ }
127
+ static deserialize(json) {
128
+ if (json.type !== ExpressionType.Const) throw new Error("Invalid expression type: " + json.type);
129
+ return new ConstExpression(json.value, json.editorConfig);
130
+ }
131
+ get responseVariableRefs() {
132
+ return [];
133
+ }
134
+ serialize() {
135
+ return {
136
+ type: this.type,
137
+ value: this.value,
138
+ editorConfig: this.editorConfig
139
+ };
140
+ }
141
+ updateItemKeyReferences(_oldItemKey, _newItemKey) {
142
+ return false;
143
+ }
144
+ };
145
+ var ResponseVariableExpression = class ResponseVariableExpression extends Expression {
146
+ variableRef;
147
+ constructor(variableRef, editorConfig) {
148
+ super(ExpressionType.ResponseVariable, editorConfig);
149
+ this.variableRef = variableRef;
150
+ this.type = ExpressionType.ResponseVariable;
151
+ }
152
+ static deserialize(json) {
153
+ if (json.type !== ExpressionType.ResponseVariable) throw new Error("Invalid expression type: " + json.type);
154
+ return new ResponseVariableExpression(json.variableRef, json.editorConfig);
155
+ }
156
+ get responseVariableRefs() {
157
+ return [new ValueReference(this.variableRef)];
158
+ }
159
+ get responseVariableRef() {
160
+ return new ValueReference(this.variableRef);
161
+ }
162
+ serialize() {
163
+ return {
164
+ type: this.type,
165
+ variableRef: this.variableRef,
166
+ editorConfig: this.editorConfig
167
+ };
168
+ }
169
+ };
170
+ var ContextVariableExpression = class ContextVariableExpression extends Expression {
171
+ contextType;
172
+ key;
173
+ arguments;
174
+ asType;
175
+ constructor(contextType, key, args, asType, editorConfig) {
176
+ super(ExpressionType.ContextVariable, editorConfig);
177
+ this.type = ExpressionType.ContextVariable;
178
+ this.contextType = contextType;
179
+ this.key = key;
180
+ this.arguments = args;
181
+ this.asType = asType;
182
+ }
183
+ static deserialize(json) {
184
+ if (json.type !== ExpressionType.ContextVariable) throw new Error("Invalid expression type: " + json.type);
185
+ return new ContextVariableExpression(json.contextType, Expression.deserialize(json.key), json.arguments?.map((arg) => Expression.deserialize(arg)), json.asType, json.editorConfig);
186
+ }
187
+ get responseVariableRefs() {
188
+ return this.arguments?.flatMap((arg) => arg?.responseVariableRefs).filter((ref) => ref !== void 0) ?? [];
189
+ }
190
+ serialize() {
191
+ return {
192
+ type: this.type,
193
+ contextType: this.contextType,
194
+ key: this.key?.serialize(),
195
+ arguments: this.arguments?.map((arg) => arg?.serialize()),
196
+ asType: this.asType,
197
+ editorConfig: this.editorConfig
198
+ };
199
+ }
200
+ };
201
+ let FunctionExpressionNames = /* @__PURE__ */ function(FunctionExpressionNames) {
202
+ FunctionExpressionNames["and"] = "and";
203
+ FunctionExpressionNames["or"] = "or";
204
+ FunctionExpressionNames["not"] = "not";
205
+ FunctionExpressionNames["list_contains"] = "list_contains";
206
+ FunctionExpressionNames["eq"] = "eq";
207
+ FunctionExpressionNames["gt"] = "gt";
208
+ FunctionExpressionNames["gte"] = "gte";
209
+ FunctionExpressionNames["lt"] = "lt";
210
+ FunctionExpressionNames["lte"] = "lte";
211
+ FunctionExpressionNames["in_range"] = "in_range";
212
+ FunctionExpressionNames["sum"] = "sum";
213
+ FunctionExpressionNames["min"] = "min";
214
+ FunctionExpressionNames["max"] = "max";
215
+ FunctionExpressionNames["str_eq"] = "str_eq";
216
+ FunctionExpressionNames["date_eq"] = "date_eq";
217
+ return FunctionExpressionNames;
218
+ }({});
219
+ var FunctionExpression = class FunctionExpression extends Expression {
220
+ functionName;
221
+ arguments;
222
+ constructor(functionName, args, editorConfig) {
223
+ super(ExpressionType.Function);
224
+ this.type = ExpressionType.Function;
225
+ this.functionName = functionName;
226
+ this.arguments = args;
227
+ this.editorConfig = editorConfig;
228
+ }
229
+ static deserialize(json) {
230
+ if (json.type !== ExpressionType.Function) throw new Error("Invalid expression type: " + json.type);
231
+ const functionName = json.functionName;
232
+ if (!Object.values(FunctionExpressionNames).includes(functionName)) throw new Error("Invalid function name: " + functionName);
233
+ const expr = new FunctionExpression(functionName, json.arguments.map((arg) => Expression.deserialize(arg)));
234
+ expr.editorConfig = json.editorConfig;
235
+ return expr;
236
+ }
237
+ get responseVariableRefs() {
238
+ const refStrings = this.arguments.flatMap((arg) => arg?.responseVariableRefs).filter((ref) => ref !== void 0).map((ref) => ref.toString());
239
+ return [...new Set(refStrings)].map((ref) => new ValueReference(ref));
240
+ }
241
+ serialize() {
242
+ return {
243
+ type: this.type,
244
+ functionName: this.functionName,
245
+ arguments: this.arguments.map((arg) => arg?.serialize()),
246
+ editorConfig: this.editorConfig
247
+ };
248
+ }
249
+ };
250
+
251
+ //#endregion
252
+ //#region src/expressions/template-value.ts
253
+ let TemplateDefTypes = /* @__PURE__ */ function(TemplateDefTypes) {
254
+ TemplateDefTypes["Default"] = "default";
255
+ TemplateDefTypes["Date2String"] = "date2string";
256
+ return TemplateDefTypes;
257
+ }({});
258
+ const serializeTemplateValue = (templateValue) => {
259
+ const json = {
260
+ type: templateValue.type,
261
+ returnType: templateValue.returnType,
262
+ expression: templateValue.expression?.serialize()
263
+ };
264
+ if (templateValue.type === TemplateDefTypes.Date2String) json.dateFormat = templateValue.dateFormat;
265
+ return json;
266
+ };
267
+ const serializeTemplateValues = (templateValues) => {
268
+ const json = {};
269
+ for (const [key, value] of templateValues.entries()) json[key] = serializeTemplateValue(value);
270
+ return json;
271
+ };
272
+ const deserializeTemplateValue = (json) => {
273
+ return {
274
+ type: json.type,
275
+ expression: json.expression ? Expression.deserialize(json.expression) : void 0,
276
+ returnType: json.returnType,
277
+ dateFormat: json.dateFormat
278
+ };
279
+ };
280
+ const deserializeTemplateValues = (json) => {
281
+ return new Map(Object.entries(json).map(([key, value]) => [key, deserializeTemplateValue(value)]));
282
+ };
283
+
284
+ //#endregion
285
+ //#region src/survey/items/utils.ts
286
+ const displayConditionsFromJson = (json) => {
287
+ return {
288
+ root: json.root ? Expression.deserialize(json.root) : void 0,
289
+ components: json.components ? Object.fromEntries(Object.entries(json.components).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0
290
+ };
291
+ };
292
+ const disabledConditionsFromJson = (json) => {
293
+ return { components: json.components ? Object.fromEntries(Object.entries(json.components).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0 };
294
+ };
295
+
296
+ //#endregion
297
+ //#region src/survey/items/survey-item.ts
298
+ /**
299
+ * Base class for survey item handlers.
300
+ * Each handler holds type, id, key, and config (properly typed) and implements core methods.
301
+ * Subclass for each item type; the core works with SurveyItemHandler only—no casting needed.
302
+ */
303
+ var SurveyItemCore = class {
304
+ id;
305
+ key;
306
+ config;
307
+ _rawItem;
308
+ displayConditions;
309
+ disabledConditions;
310
+ validations;
311
+ prefillRules;
312
+ constructor(rawItem) {
313
+ this._rawItem = rawItem;
314
+ this.updateExpressions();
315
+ this.id = this._rawItem.id;
316
+ this.key = this._rawItem.key;
317
+ this.config = this.parseConfig(this._rawItem.config);
318
+ }
319
+ get rawItem() {
320
+ return this._rawItem;
321
+ }
322
+ updateRawItem(rawItem) {
323
+ if (rawItem.id !== this.id) throw new Error("Cannot update raw item with a different id");
324
+ this._rawItem = rawItem;
325
+ this.updateExpressions();
326
+ this.key = rawItem.key;
327
+ this.config = this.parseConfig(rawItem.config);
328
+ }
329
+ updateExpressions() {
330
+ this.displayConditions = this._rawItem.displayConditions ? displayConditionsFromJson(this._rawItem.displayConditions) : void 0;
331
+ this.disabledConditions = this._rawItem.disabledConditions ? disabledConditionsFromJson(this._rawItem.disabledConditions) : void 0;
332
+ this.validations = this._rawItem.validations ? Object.fromEntries(Object.entries(this._rawItem.validations).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0;
333
+ this.prefillRules = this._rawItem.prefillRules ? this._rawItem.prefillRules.map((rule) => rule ? Expression.deserialize(rule) : void 0) : void 0;
334
+ }
335
+ getReferenceUsages() {
336
+ const usages = [];
337
+ if (this.displayConditions) {
338
+ for (const ref of this.displayConditions.root?.responseVariableRefs || []) usages.push({
339
+ itemId: this.id,
340
+ usageType: ReferenceUsageType.displayConditions,
341
+ valueReference: ref
342
+ });
343
+ for (const [componentKey, expression] of Object.entries(this.displayConditions.components || {})) for (const ref of expression?.responseVariableRefs || []) usages.push({
344
+ itemId: this.id,
345
+ fullComponentKey: componentKey,
346
+ usageType: ReferenceUsageType.displayConditions,
347
+ valueReference: ref
348
+ });
349
+ }
350
+ if (this.disabledConditions) for (const [componentKey, expression] of Object.entries(this.disabledConditions.components || {})) for (const ref of expression?.responseVariableRefs || []) usages.push({
351
+ itemId: this.id,
352
+ fullComponentKey: componentKey,
353
+ usageType: ReferenceUsageType.disabledConditions,
354
+ valueReference: ref
355
+ });
356
+ if (this.validations) for (const [validationKey, expression] of Object.entries(this.validations)) for (const ref of expression?.responseVariableRefs || []) usages.push({
357
+ itemId: this.id,
358
+ fullComponentKey: validationKey,
359
+ usageType: ReferenceUsageType.validations,
360
+ valueReference: ref
361
+ });
362
+ return usages;
363
+ }
364
+ };
365
+
366
+ //#endregion
367
+ //#region src/survey/items/types.ts
368
+ let ReservedSurveyItemTypes = /* @__PURE__ */ function(ReservedSurveyItemTypes) {
369
+ ReservedSurveyItemTypes["Group"] = "group";
370
+ ReservedSurveyItemTypes["PageBreak"] = "page-break";
371
+ return ReservedSurveyItemTypes;
372
+ }({});
373
+
374
+ //#endregion
375
+ //#region src/survey/item-key.ts
376
+ /**
377
+ * SurveyItemKey stores the key of the item and the path to the item.
378
+ */
379
+ var SurveyItemKey = class {
380
+ _itemKey;
381
+ _path;
382
+ _keySeparator;
383
+ constructor(itemKey, path, keySeparator = ".") {
384
+ this._itemKey = itemKey;
385
+ this._path = path ?? [];
386
+ this._keySeparator = keySeparator;
387
+ }
388
+ get itemKey() {
389
+ return this._itemKey;
390
+ }
391
+ get path() {
392
+ return this._path;
393
+ }
394
+ get parentFullKey() {
395
+ return this._path.join(this._keySeparator);
396
+ }
397
+ get fullKey() {
398
+ return [...this._path, this._itemKey].join(this._keySeparator);
399
+ }
400
+ get isRoot() {
401
+ return this._path.length === 0;
402
+ }
403
+ get keySeparator() {
404
+ return this._keySeparator;
405
+ }
406
+ set keySeparator(keySeparator) {
407
+ this._keySeparator = keySeparator;
408
+ }
409
+ };
410
+
411
+ //#endregion
412
+ //#region src/survey/survey-file-schema.ts
413
+ const CURRENT_SURVEY_SCHEMA = "https://github.com/case-framework/case-survey-toolkit/packages/survey-core/schemas/survey-schema.json";
414
+
415
+ //#endregion
416
+ //#region src/survey/utils/translations.ts
417
+ const validateLocale = (locale) => {
418
+ if (locale.trim() === "") throw new Error("Locale cannot be empty");
419
+ };
420
+ var SurveyItemTranslations = class {
421
+ _translations;
422
+ constructor() {
423
+ this._translations = {};
424
+ }
425
+ setContent(locale, contentKey, content) {
426
+ validateLocale(locale);
427
+ if (!this._translations?.[locale]) {
428
+ if (!content) return;
429
+ this._translations[locale] = {};
430
+ }
431
+ if (!content) delete this._translations[locale][contentKey];
432
+ else this._translations[locale][contentKey] = content;
433
+ }
434
+ setAllForLocale(locale, content) {
435
+ validateLocale(locale);
436
+ if (!this._translations?.[locale]) {
437
+ if (!content) return;
438
+ this._translations[locale] = {};
439
+ }
440
+ this._translations[locale] = content || {};
441
+ }
442
+ get locales() {
443
+ return Object.keys(this._translations || {});
444
+ }
445
+ getAllForLocale(locale) {
446
+ return this._translations?.[locale];
447
+ }
448
+ getContent(locale, contentKey, fallbackLocale) {
449
+ const content = this._translations?.[locale]?.[contentKey];
450
+ if (content) return content;
451
+ if (fallbackLocale) return this._translations?.[fallbackLocale]?.[contentKey];
452
+ }
453
+ };
454
+ var SurveyTranslations = class {
455
+ _translations;
456
+ constructor(translations) {
457
+ this._translations = translations || {};
458
+ }
459
+ serialize() {
460
+ if (this.locales.length === 0) return;
461
+ return this._translations;
462
+ }
463
+ get locales() {
464
+ return Object.keys(this._translations);
465
+ }
466
+ removeLocale(locale) {
467
+ validateLocale(locale);
468
+ delete this._translations[locale];
469
+ }
470
+ renameLocale(oldLocale, newLocale) {
471
+ validateLocale(oldLocale);
472
+ validateLocale(newLocale);
473
+ if (this._translations[oldLocale]) {
474
+ this._translations[newLocale] = this._translations[oldLocale];
475
+ delete this._translations[oldLocale];
476
+ }
477
+ }
478
+ cloneLocaleAs(locale, newLocale) {
479
+ validateLocale(locale);
480
+ validateLocale(newLocale);
481
+ if (this._translations[locale]) this._translations[newLocale] = structuredCloneMethod(this._translations[locale]);
482
+ }
483
+ get surveyCardContent() {
484
+ const translations = {};
485
+ for (const locale of this.locales) {
486
+ const contentForLocale = this._translations?.[locale]?.surveyCardContent;
487
+ if (contentForLocale) translations[locale] = contentForLocale;
488
+ }
489
+ return translations;
490
+ }
491
+ get navigationContent() {
492
+ const translations = {};
493
+ for (const locale of this.locales) {
494
+ const contentForLocale = this._translations?.[locale]?.navigationContent;
495
+ if (contentForLocale) translations[locale] = contentForLocale;
496
+ }
497
+ return translations;
498
+ }
499
+ get validationMessages() {
500
+ const translations = {};
501
+ for (const locale of this.locales) {
502
+ const contentForLocale = this._translations?.[locale]?.validationMessages;
503
+ if (contentForLocale) translations[locale] = contentForLocale;
504
+ }
505
+ return translations;
506
+ }
507
+ setSurveyCardContent(locale, content) {
508
+ validateLocale(locale);
509
+ if (!this._translations[locale]) {
510
+ if (!content) return;
511
+ this._translations[locale] = {};
512
+ }
513
+ this._translations[locale].surveyCardContent = content;
514
+ }
515
+ setNavigationContent(locale, content) {
516
+ validateLocale(locale);
517
+ if (!this._translations[locale]) {
518
+ if (!content) return;
519
+ this._translations[locale] = {};
520
+ }
521
+ this._translations[locale].navigationContent = content;
522
+ }
523
+ setValidationMessages(locale, content) {
524
+ validateLocale(locale);
525
+ if (!this._translations[locale]) {
526
+ if (!content) return;
527
+ this._translations[locale] = {};
528
+ }
529
+ this._translations[locale].validationMessages = content;
530
+ }
531
+ getItemTranslations(itemId) {
532
+ const itemTranslations = new SurveyItemTranslations();
533
+ for (const locale of this.locales) {
534
+ const contentForLocale = this._translations?.[locale].itemTranslations?.[itemId];
535
+ itemTranslations.setAllForLocale(locale, contentForLocale);
536
+ }
537
+ return itemTranslations;
538
+ }
539
+ setItemTranslations(itemId, itemContent) {
540
+ itemContent?.locales.forEach((locale) => validateLocale(locale));
541
+ if (!itemContent) {
542
+ for (const locale of this.locales) if (this._translations[locale].itemTranslations?.[itemId]) delete this._translations[locale].itemTranslations?.[itemId];
543
+ } else {
544
+ const localesInUpdate = itemContent.locales;
545
+ for (const locale of localesInUpdate) if (!this.locales.includes(locale)) this._translations[locale] = {};
546
+ for (const locale of this.locales) if (localesInUpdate.includes(locale)) {
547
+ if (!this._translations[locale]) this._translations[locale] = {};
548
+ if (!this._translations[locale].itemTranslations) this._translations[locale].itemTranslations = {};
549
+ this._translations[locale].itemTranslations[itemId] = itemContent.getAllForLocale(locale) ?? {};
550
+ } else delete this._translations[locale].itemTranslations[itemId];
551
+ }
552
+ }
553
+ /**
554
+ * Rename a component key (within an item) - update key in all translations and remove old key
555
+ * @param itemKey - The key of the item
556
+ * @param oldKey - The old key of the component
557
+ * @param newKey - The new key of the component
558
+ */
559
+ onComponentKeyChanged(itemKey, oldKey, newKey) {
560
+ for (const locale of this.locales) {
561
+ const itemTranslations = this._translations?.[locale].itemTranslations?.[itemKey];
562
+ if (itemTranslations) {
563
+ for (const key of Object.keys(itemTranslations)) if (key.startsWith(oldKey + ".") || key === oldKey) {
564
+ itemTranslations[key.replace(oldKey, newKey)] = { ...itemTranslations[key] };
565
+ delete itemTranslations[key];
566
+ }
567
+ }
568
+ }
569
+ }
570
+ /**
571
+ * Remove all translations for a component
572
+ * @param itemId - The id of the item
573
+ * @param componentKey - The key of the component
574
+ */
575
+ onComponentDeleted(itemId, componentKey) {
576
+ for (const locale of this.locales) {
577
+ const itemTranslations = this._translations?.[locale].itemTranslations?.[itemId];
578
+ if (itemTranslations) {
579
+ for (const key of Object.keys(itemTranslations)) if (key.startsWith(componentKey + ".") || key === componentKey) delete itemTranslations[key];
580
+ }
581
+ }
582
+ }
583
+ /**
584
+ * Remove all translations for an item
585
+ * @param id - The id of the item
586
+ */
587
+ onItemDeleted(id) {
588
+ for (const locale of this.locales) delete this._translations[locale].itemTranslations?.[id];
589
+ }
590
+ };
591
+
592
+ //#endregion
593
+ //#region src/survey/utils/group-utils.ts
594
+ /**
595
+ * Shared utilities for array operations.
596
+ */
597
+ /**
598
+ * Swap two elements in an array by index.
599
+ * @returns A new array with the elements swapped
600
+ */
601
+ function swapArrayElements(arr, fromIndex, toIndex) {
602
+ const result = [...arr];
603
+ [result[fromIndex], result[toIndex]] = [result[toIndex], result[fromIndex]];
604
+ return result;
605
+ }
606
+ /**
607
+ * Move an element from one index to another.
608
+ * @returns A new array with the element moved
609
+ */
610
+ function moveArrayElement(arr, fromIndex, toIndex) {
611
+ const result = [...arr];
612
+ const [moved] = result.splice(fromIndex, 1);
613
+ result.splice(toIndex, 0, moved);
614
+ return result;
615
+ }
616
+
617
+ //#endregion
618
+ //#region src/survey/registry/built-in-items.ts
619
+ var GroupItemCore = class extends SurveyItemCore {
620
+ type = ReservedSurveyItemTypes.Group;
621
+ parseConfig(rawConfig) {
622
+ const cfg = rawConfig ?? {};
623
+ return {
624
+ items: cfg.items,
625
+ shuffleItems: cfg.shuffleItems,
626
+ isRoot: cfg.isRoot
627
+ };
628
+ }
629
+ serializeConfig() {
630
+ return this.config;
631
+ }
632
+ isInteractive() {
633
+ return false;
634
+ }
635
+ getAvailableResponseValueSlots() {
636
+ return {};
637
+ }
638
+ isRoot() {
639
+ return this.config.isRoot ?? false;
640
+ }
641
+ get items() {
642
+ return this.config.items ?? [];
643
+ }
644
+ /**
645
+ * Add a child item id to this group at the given index.
646
+ * @param itemId The id of the item to add to this group.
647
+ * @param index The index at which to add the item. If not provided, the item is added to the end of the group.
648
+ */
649
+ addChild(itemId, index) {
650
+ const items = this.config.items ??= [];
651
+ const insertIndex = index !== void 0 ? Math.min(index, items.length) : items.length;
652
+ items.splice(insertIndex, 0, itemId);
653
+ this.updateRawItem({
654
+ ...this.rawItem,
655
+ config: { ...this.config }
656
+ });
657
+ }
658
+ /**
659
+ * Remove a child item id from this group.
660
+ * @param itemId The id of the item to remove from this group.
661
+ */
662
+ removeChild(itemId) {
663
+ const items = this.config.items ??= [];
664
+ const index = items.indexOf(itemId);
665
+ if (index !== -1) {
666
+ items.splice(index, 1);
667
+ this.updateRawItem({
668
+ ...this.rawItem,
669
+ config: { ...this.config }
670
+ });
671
+ }
672
+ }
673
+ /**
674
+ * Check if an item is a direct child of this group.
675
+ */
676
+ hasChild(itemId) {
677
+ return this.items.includes(itemId);
678
+ }
679
+ /** Whether items in this group should be shuffled. */
680
+ get shuffleItems() {
681
+ return this.config.shuffleItems ?? false;
682
+ }
683
+ set shuffleItems(value) {
684
+ this.config.shuffleItems = value;
685
+ this.updateRawItem({
686
+ ...this.rawItem,
687
+ config: { ...this.config }
688
+ });
689
+ }
690
+ /**
691
+ * Get the ordered list of child item ids.
692
+ */
693
+ getChildrenIds() {
694
+ return [...this.items];
695
+ }
696
+ /**
697
+ * Swap two items by their positions in the group.
698
+ * @throws Error if indices are out of bounds
699
+ */
700
+ swapItemsByIndex(from, to) {
701
+ const items = this.items;
702
+ 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}`);
703
+ if (from === to) return;
704
+ this.config.items = swapArrayElements(items, from, to);
705
+ this.updateRawItem({
706
+ ...this.rawItem,
707
+ config: { ...this.config }
708
+ });
709
+ }
710
+ /**
711
+ * Swap two items by their ids.
712
+ * @throws Error if either id is not found in this group
713
+ */
714
+ swapItemsById(id, withId) {
715
+ const items = this.items;
716
+ const index1 = items.indexOf(id);
717
+ const index2 = items.indexOf(withId);
718
+ if (index1 === -1) throw new Error(`Item '${id}' not found in group '${this.key}'`);
719
+ if (index2 === -1) throw new Error(`Item '${withId}' not found in group '${this.key}'`);
720
+ this.swapItemsByIndex(index1, index2);
721
+ }
722
+ /**
723
+ * Move an item by id to a specific index.
724
+ * @throws Error if the item is not found or index is out of bounds
725
+ */
726
+ moveItemToIndex(id, index) {
727
+ const items = this.items;
728
+ const fromIndex = items.indexOf(id);
729
+ if (fromIndex === -1) throw new Error(`Item '${id}' not found in group '${this.key}'`);
730
+ if (index < 0 || index >= items.length) throw new Error(`Index out of bounds. Valid range is 0-${items.length - 1}`);
731
+ if (fromIndex === index) return;
732
+ this.config.items = moveArrayElement(items, fromIndex, index);
733
+ this.updateRawItem({
734
+ ...this.rawItem,
735
+ config: { ...this.config }
736
+ });
737
+ }
738
+ };
739
+ var PageBreakItemCore = class extends SurveyItemCore {
740
+ type = ReservedSurveyItemTypes.PageBreak;
741
+ parseConfig(_rawConfig) {
742
+ return {};
743
+ }
744
+ serializeConfig() {}
745
+ isInteractive() {
746
+ return false;
747
+ }
748
+ getAvailableResponseValueSlots() {
749
+ return {};
750
+ }
751
+ };
752
+ const builtInItemCoreRegistry = {
753
+ [ReservedSurveyItemTypes.Group]: GroupItemCore,
754
+ [ReservedSurveyItemTypes.PageBreak]: PageBreakItemCore
755
+ };
756
+ const isBuiltInItemType = (itemType) => itemType in builtInItemCoreRegistry;
757
+
758
+ //#endregion
759
+ //#region src/survey/registry/item-registry.ts
760
+ /**
761
+ * Creates a handler instance for a raw survey item.
762
+ * Uses built-in registry for group/page-break, otherwise the provided plugin registry.
763
+ */
764
+ function createItemCore(rawItem, pluginRegistry) {
765
+ if (isBuiltInItemType(rawItem.itemType)) {
766
+ const HandlerClass = builtInItemCoreRegistry[rawItem.itemType];
767
+ return new HandlerClass(rawItem);
768
+ }
769
+ if (pluginRegistry && rawItem.itemType in pluginRegistry) {
770
+ const HandlerClass = pluginRegistry[rawItem.itemType];
771
+ return new HandlerClass(rawItem);
772
+ }
773
+ throw new Error(`Unknown item type: ${rawItem.itemType}`);
774
+ }
775
+
776
+ //#endregion
777
+ //#region src/survey/survey.ts
778
+ var Survey = class Survey {
779
+ maxItemsPerPage;
780
+ metadata;
781
+ surveyItems = /* @__PURE__ */ new Map();
782
+ _templateValues = /* @__PURE__ */ new Map();
783
+ _translations;
784
+ constructor(pluginRegistry) {
785
+ this.pluginRegistry = pluginRegistry;
786
+ this._translations = new SurveyTranslations();
787
+ }
788
+ /**
789
+ * Create a survey item from raw JSON data.
790
+ * Uses the survey's plugin registry when available.
791
+ */
792
+ createItemFromRaw(rawItem) {
793
+ return createItemCore(rawItem, this.pluginRegistry);
794
+ }
795
+ static fromJson(json, pluginRegistry) {
796
+ const survey = new Survey(pluginRegistry);
797
+ const rawSurvey = json;
798
+ if (rawSurvey.$schema !== CURRENT_SURVEY_SCHEMA) throw new Error(`Unsupported survey schema: ${rawSurvey.$schema}`);
799
+ survey.surveyItems = /* @__PURE__ */ new Map();
800
+ if (!rawSurvey.surveyItems || rawSurvey.surveyItems.length === 0) throw new Error("surveyItems is required");
801
+ rawSurvey.surveyItems.forEach((item) => {
802
+ const surveyItem = createItemCore(item, pluginRegistry);
803
+ survey.surveyItems.set(surveyItem.id, surveyItem);
804
+ });
805
+ if (rawSurvey.templateValues) survey._templateValues = deserializeTemplateValues(rawSurvey.templateValues);
806
+ survey._translations = new SurveyTranslations(rawSurvey.translations);
807
+ if (rawSurvey.maxItemsPerPage) survey.maxItemsPerPage = rawSurvey.maxItemsPerPage;
808
+ if (rawSurvey.metadata) survey.metadata = rawSurvey.metadata;
809
+ return survey;
810
+ }
811
+ serialize() {
812
+ const json = {
813
+ $schema: CURRENT_SURVEY_SCHEMA,
814
+ surveyItems: Array.from(this.surveyItems.values()).map((item) => item.rawItem)
815
+ };
816
+ json.translations = this._translations?.serialize();
817
+ if (this._templateValues) json.templateValues = serializeTemplateValues(this._templateValues);
818
+ if (this.maxItemsPerPage) json.maxItemsPerPage = this.maxItemsPerPage;
819
+ if (this.metadata) json.metadata = this.metadata;
820
+ return json;
821
+ }
822
+ get surveyKey() {
823
+ if (!this.rootItem) return;
824
+ return this.rootItem.key;
825
+ }
826
+ get locales() {
827
+ return this._translations?.locales || [];
828
+ }
829
+ get rootItem() {
830
+ return Array.from(this.surveyItems.values()).find((item) => item instanceof GroupItemCore && item.isRoot);
831
+ }
832
+ get keyIndex() {
833
+ const index = [];
834
+ for (const item of this.surveyItems.values()) {
835
+ const path = this.getItemPath(item.id);
836
+ index.push({
837
+ itemId: item.id,
838
+ key: item.key,
839
+ keyPath: path.map((id) => this.surveyItems.get(id)?.key ?? ""),
840
+ path
841
+ });
842
+ }
843
+ return index;
844
+ }
845
+ getItemPath(itemId) {
846
+ const item = this.surveyItems.get(itemId);
847
+ if (!item) throw new Error(`Item ${itemId} not found`);
848
+ if (item instanceof GroupItemCore && item.isRoot()) return [];
849
+ const parentItem = this.getParentItem(itemId);
850
+ if (!parentItem) throw new Error(`Parent item for ${itemId} not found`);
851
+ return [...this.getItemPath(parentItem.id), parentItem.id];
852
+ }
853
+ getItemKey(itemId) {
854
+ const item = this.surveyItems.get(itemId);
855
+ if (!item) return;
856
+ const keyPath = this.getItemPath(itemId).map((id) => this.surveyItems.get(id)?.key ?? "");
857
+ return new SurveyItemKey(item.key, keyPath);
858
+ }
859
+ getParentItem(itemId) {
860
+ const item = this.surveyItems.get(itemId);
861
+ if (!item) throw new Error(`Item ${itemId} not found`);
862
+ if (item instanceof GroupItemCore && item.isRoot()) return;
863
+ const parentItem = Array.from(this.surveyItems.values()).find((item) => item instanceof GroupItemCore && item.config.items?.includes(itemId));
864
+ if (!parentItem) return;
865
+ return parentItem;
866
+ }
867
+ getChildrenItems(parentId) {
868
+ const parentItem = this.surveyItems.get(parentId);
869
+ if (!parentItem || !(parentItem instanceof GroupItemCore)) return [];
870
+ return parentItem.config.items?.map((id) => this.surveyItems.get(id))?.filter((item) => item !== void 0) || [];
871
+ }
872
+ getSiblings(itemId) {
873
+ if (!this.surveyItems.get(itemId)) return [];
874
+ return this.getParentItem(itemId)?.items?.map((id) => this.surveyItems.get(id))?.filter((item) => item !== void 0) || [];
875
+ }
876
+ get translations() {
877
+ if (!this._translations) this._translations = new SurveyTranslations();
878
+ return this._translations;
879
+ }
880
+ getItemTranslations(id) {
881
+ if (!this.surveyItems.get(id)) throw new Error(`Item ${id} not found`);
882
+ return this._translations?.getItemTranslations(id);
883
+ }
884
+ getTemplateValue(templateValueKey) {
885
+ return this._templateValues?.get(templateValueKey);
886
+ }
887
+ setTemplateValue(templateValueKey, templateValue) {
888
+ if (!this._templateValues) this._templateValues = /* @__PURE__ */ new Map();
889
+ this._templateValues?.set(templateValueKey, templateValue);
890
+ }
891
+ deleteTemplateValue(templateValueKey) {
892
+ this._templateValues?.delete(templateValueKey);
893
+ }
894
+ getTemplateValueKeys() {
895
+ return Array.from(this._templateValues?.keys() || []);
896
+ }
897
+ getAvailableResponseValueSlots(byType) {
898
+ let valueRefs = {};
899
+ for (const item of this.surveyItems.values()) valueRefs = {
900
+ ...valueRefs,
901
+ ...item.getAvailableResponseValueSlots(byType)
902
+ };
903
+ return valueRefs;
904
+ }
905
+ /**
906
+ * Get all reference usages for the survey
907
+ * @param forItemId - optional item id to filter usages for a specific item and its children (if not provided, all usages are returned)
908
+ * @returns all reference usages for the survey (or for a specific item and its children)
909
+ */
910
+ getReferenceUsages(forItemId) {
911
+ const usages = [];
912
+ for (const item of this.surveyItems.values()) {
913
+ if (forItemId && item.id !== forItemId && !this.isDescendantOf(item.id, forItemId)) continue;
914
+ usages.push(...item.getReferenceUsages());
915
+ }
916
+ if (this._templateValues) for (const [templateValueKey, templateValue] of this._templateValues.entries()) for (const ref of templateValue.expression?.responseVariableRefs || []) usages.push({
917
+ itemId: templateValueKey,
918
+ usageType: ReferenceUsageType.templateValues,
919
+ valueReference: ref
920
+ });
921
+ return usages;
922
+ }
923
+ isDescendantOf(targetId, ancestorId) {
924
+ if (targetId === ancestorId) return true;
925
+ return this.getItemPath(targetId).includes(ancestorId);
926
+ }
927
+ };
928
+
929
+ //#endregion
930
+ export { ValueReference as C, structuredCloneMethod as D, shuffleIndices as E, ReferenceUsageType as S, generateId as T, Expression as _, validateLocale as a, FunctionExpressionNames as b, SurveyItemCore as c, deserializeTemplateValues as d, serializeTemplateValue as f, ContextVariableType as g, ContextVariableExpression as h, SurveyTranslations as i, TemplateDefTypes as l, ConstExpression as m, GroupItemCore as n, SurveyItemKey as o, serializeTemplateValues as p, SurveyItemTranslations as r, ReservedSurveyItemTypes as s, Survey as t, deserializeTemplateValue as u, ExpressionType as v, ValueReferenceMethod as w, ResponseVariableExpression as x, FunctionExpression as y };
931
+ //# sourceMappingURL=survey-C3ZHI-5z.mjs.map