@case-framework/survey-core 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/editor.d.mts +52 -12
- package/build/editor.d.mts.map +1 -1
- package/build/editor.mjs +223 -48
- package/build/editor.mjs.map +1 -1
- package/build/index.d.mts +80 -50
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +235 -169
- package/build/index.mjs.map +1 -1
- package/build/package.json +8 -8
- package/build/{survey-D3sMutUV.d.mts → survey-DShEOjyz.d.mts} +389 -82
- package/build/survey-DShEOjyz.d.mts.map +1 -0
- package/build/{survey-DQmpzihl.mjs → survey-yXdl8xkf.mjs} +479 -48
- package/build/survey-yXdl8xkf.mjs.map +1 -0
- package/package.json +8 -8
- package/build/survey-D3sMutUV.d.mts.map +0 -1
- package/build/survey-DQmpzihl.mjs.map +0 -1
|
@@ -1,16 +1,37 @@
|
|
|
1
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
|
+
}
|
|
2
28
|
/**
|
|
3
29
|
* Shuffles an array of indices using the Fisher-Yates shuffle algorithm
|
|
4
30
|
* @param length - The length of the array to create indices for
|
|
5
31
|
* @returns A shuffled array of indices from 0 to length-1
|
|
6
32
|
*/
|
|
7
|
-
function shuffleIndices(length) {
|
|
8
|
-
|
|
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;
|
|
33
|
+
function shuffleIndices(length, random = Math.random) {
|
|
34
|
+
return shuffleArray(Array.from({ length }, (_, i) => i), random);
|
|
14
35
|
}
|
|
15
36
|
function structuredCloneMethod(obj) {
|
|
16
37
|
if (typeof structuredClone !== "undefined") return structuredClone(obj);
|
|
@@ -44,7 +65,6 @@ function generateCodingKey(length = 4) {
|
|
|
44
65
|
for (let i = 0; i < length; i++) key += CODING_KEY_ALPHABET.charAt(Math.floor(Math.random() * base));
|
|
45
66
|
return key;
|
|
46
67
|
}
|
|
47
|
-
|
|
48
68
|
//#endregion
|
|
49
69
|
//#region src/survey/utils/value-reference.ts
|
|
50
70
|
const SEPARATOR = "...";
|
|
@@ -88,9 +108,9 @@ let ReferenceUsageType = /* @__PURE__ */ function(ReferenceUsageType) {
|
|
|
88
108
|
ReferenceUsageType["templateValues"] = "templateValues";
|
|
89
109
|
ReferenceUsageType["validations"] = "validations";
|
|
90
110
|
ReferenceUsageType["disabledConditions"] = "disabledConditions";
|
|
111
|
+
ReferenceUsageType["prefills"] = "prefills";
|
|
91
112
|
return ReferenceUsageType;
|
|
92
113
|
}({});
|
|
93
|
-
|
|
94
114
|
//#endregion
|
|
95
115
|
//#region src/expressions/expression.ts
|
|
96
116
|
const ExpressionType = {
|
|
@@ -260,7 +280,6 @@ var FunctionExpression = class FunctionExpression extends Expression {
|
|
|
260
280
|
};
|
|
261
281
|
}
|
|
262
282
|
};
|
|
263
|
-
|
|
264
283
|
//#endregion
|
|
265
284
|
//#region src/expressions/template-value.ts
|
|
266
285
|
let TemplateDefTypes = /* @__PURE__ */ function(TemplateDefTypes) {
|
|
@@ -274,7 +293,7 @@ const serializeTemplateValue = (templateValue) => {
|
|
|
274
293
|
returnType: templateValue.returnType,
|
|
275
294
|
expression: templateValue.expression?.serialize()
|
|
276
295
|
};
|
|
277
|
-
if (templateValue.type ===
|
|
296
|
+
if (templateValue.type === "date2string") json.dateFormat = templateValue.dateFormat;
|
|
278
297
|
return json;
|
|
279
298
|
};
|
|
280
299
|
const serializeTemplateValues = (templateValues) => {
|
|
@@ -293,7 +312,72 @@ const deserializeTemplateValue = (json) => {
|
|
|
293
312
|
const deserializeTemplateValues = (json) => {
|
|
294
313
|
return new Map(Object.entries(json).map(([key, value]) => [key, deserializeTemplateValue(value)]));
|
|
295
314
|
};
|
|
296
|
-
|
|
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
|
+
}
|
|
297
381
|
//#endregion
|
|
298
382
|
//#region src/survey/items/utils.ts
|
|
299
383
|
const displayConditionsFromJson = (json) => {
|
|
@@ -305,7 +389,119 @@ const displayConditionsFromJson = (json) => {
|
|
|
305
389
|
const disabledConditionsFromJson = (json) => {
|
|
306
390
|
return { components: json.components ? Object.fromEntries(Object.entries(json.components).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0 };
|
|
307
391
|
};
|
|
308
|
-
|
|
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
|
+
}
|
|
309
505
|
//#endregion
|
|
310
506
|
//#region src/survey/items/survey-item.ts
|
|
311
507
|
/**
|
|
@@ -321,7 +517,7 @@ var SurveyItemCore = class {
|
|
|
321
517
|
displayConditions;
|
|
322
518
|
disabledConditions;
|
|
323
519
|
validations;
|
|
324
|
-
|
|
520
|
+
prefills;
|
|
325
521
|
constructor(rawItem) {
|
|
326
522
|
this._rawItem = rawItem;
|
|
327
523
|
this.updateExpressions();
|
|
@@ -329,6 +525,19 @@ var SurveyItemCore = class {
|
|
|
329
525
|
this.key = this._rawItem.key;
|
|
330
526
|
this.config = this.parseConfig(this._rawItem.config);
|
|
331
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
|
+
}
|
|
332
541
|
get rawItem() {
|
|
333
542
|
return this._rawItem;
|
|
334
543
|
}
|
|
@@ -352,39 +561,61 @@ var SurveyItemCore = class {
|
|
|
352
561
|
this.displayConditions = this._rawItem.displayConditions ? displayConditionsFromJson(this._rawItem.displayConditions) : void 0;
|
|
353
562
|
this.disabledConditions = this._rawItem.disabledConditions ? disabledConditionsFromJson(this._rawItem.disabledConditions) : void 0;
|
|
354
563
|
this.validations = this._rawItem.validations ? Object.fromEntries(Object.entries(this._rawItem.validations).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0;
|
|
355
|
-
this.
|
|
564
|
+
this.prefills = this._rawItem.prefills?.map((prefill) => deserializeSurveyItemPrefill(prefill));
|
|
356
565
|
}
|
|
357
566
|
getReferenceUsages() {
|
|
358
567
|
const usages = [];
|
|
359
568
|
if (this.displayConditions) {
|
|
360
569
|
for (const ref of this.displayConditions.root?.responseVariableRefs || []) usages.push({
|
|
361
570
|
itemId: this.id,
|
|
362
|
-
usageType:
|
|
571
|
+
usageType: "displayConditions",
|
|
363
572
|
valueReference: ref
|
|
364
573
|
});
|
|
365
574
|
for (const [componentKey, expression] of Object.entries(this.displayConditions.components || {})) for (const ref of expression?.responseVariableRefs || []) usages.push({
|
|
366
575
|
itemId: this.id,
|
|
367
576
|
fullComponentKey: componentKey,
|
|
368
|
-
usageType:
|
|
577
|
+
usageType: "displayConditions",
|
|
369
578
|
valueReference: ref
|
|
370
579
|
});
|
|
371
580
|
}
|
|
372
581
|
if (this.disabledConditions) for (const [componentKey, expression] of Object.entries(this.disabledConditions.components || {})) for (const ref of expression?.responseVariableRefs || []) usages.push({
|
|
373
582
|
itemId: this.id,
|
|
374
583
|
fullComponentKey: componentKey,
|
|
375
|
-
usageType:
|
|
584
|
+
usageType: "disabledConditions",
|
|
376
585
|
valueReference: ref
|
|
377
586
|
});
|
|
378
587
|
if (this.validations) for (const [validationKey, expression] of Object.entries(this.validations)) for (const ref of expression?.responseVariableRefs || []) usages.push({
|
|
379
588
|
itemId: this.id,
|
|
380
589
|
fullComponentKey: validationKey,
|
|
381
|
-
usageType:
|
|
590
|
+
usageType: "validations",
|
|
382
591
|
valueReference: ref
|
|
383
592
|
});
|
|
593
|
+
if (this.prefills) for (const prefill of this.prefills) {
|
|
594
|
+
for (const ref of prefill.when?.responseVariableRefs || []) usages.push({
|
|
595
|
+
itemId: this.id,
|
|
596
|
+
fullComponentKey: prefill.id,
|
|
597
|
+
usageType: "prefills",
|
|
598
|
+
valueReference: ref
|
|
599
|
+
});
|
|
600
|
+
if (prefill.source.type === "expression") for (const ref of prefill.source.expression.responseVariableRefs || []) usages.push({
|
|
601
|
+
itemId: this.id,
|
|
602
|
+
fullComponentKey: prefill.id,
|
|
603
|
+
usageType: "prefills",
|
|
604
|
+
valueReference: ref
|
|
605
|
+
});
|
|
606
|
+
}
|
|
384
607
|
return usages;
|
|
385
608
|
}
|
|
609
|
+
resolvePrefillTarget(target) {
|
|
610
|
+
const slotDefinitions = this.getResponseSlotDefinitions();
|
|
611
|
+
const explicitMatch = slotDefinitions.find((slot) => slot.prefillTarget !== void 0 && prefillTargetsEqual(slot.prefillTarget, target));
|
|
612
|
+
if (explicitMatch) return explicitMatch;
|
|
613
|
+
if (target.type === SurveyItemPrefillTargetType.itemResponse && slotDefinitions.length === 1) return slotDefinitions[0];
|
|
614
|
+
}
|
|
615
|
+
normalizePrefillValue(_prefill, targetSlot, value) {
|
|
616
|
+
return value.type === targetSlot.valueType ? value : void 0;
|
|
617
|
+
}
|
|
386
618
|
};
|
|
387
|
-
|
|
388
619
|
//#endregion
|
|
389
620
|
//#region src/survey/items/types.ts
|
|
390
621
|
let ReservedSurveyItemTypes = /* @__PURE__ */ function(ReservedSurveyItemTypes) {
|
|
@@ -392,7 +623,6 @@ let ReservedSurveyItemTypes = /* @__PURE__ */ function(ReservedSurveyItemTypes)
|
|
|
392
623
|
ReservedSurveyItemTypes["PageBreak"] = "page-break";
|
|
393
624
|
return ReservedSurveyItemTypes;
|
|
394
625
|
}({});
|
|
395
|
-
|
|
396
626
|
//#endregion
|
|
397
627
|
//#region src/survey/item-key.ts
|
|
398
628
|
/**
|
|
@@ -429,7 +659,6 @@ var SurveyItemKey = class {
|
|
|
429
659
|
this._keySeparator = keySeparator;
|
|
430
660
|
}
|
|
431
661
|
};
|
|
432
|
-
|
|
433
662
|
//#endregion
|
|
434
663
|
//#region src/survey/utils/group-utils.ts
|
|
435
664
|
/**
|
|
@@ -454,11 +683,10 @@ function moveArrayElement(arr, fromIndex, toIndex) {
|
|
|
454
683
|
result.splice(toIndex, 0, moved);
|
|
455
684
|
return result;
|
|
456
685
|
}
|
|
457
|
-
|
|
458
686
|
//#endregion
|
|
459
687
|
//#region src/survey/registry/built-in-items.ts
|
|
460
688
|
var GroupItemCore = class extends SurveyItemCore {
|
|
461
|
-
type =
|
|
689
|
+
type = "group";
|
|
462
690
|
parseConfig(rawConfig) {
|
|
463
691
|
const cfg = rawConfig ?? {};
|
|
464
692
|
return {
|
|
@@ -473,8 +701,8 @@ var GroupItemCore = class extends SurveyItemCore {
|
|
|
473
701
|
isInteractive() {
|
|
474
702
|
return false;
|
|
475
703
|
}
|
|
476
|
-
|
|
477
|
-
return
|
|
704
|
+
getResponseSlotDefinitions() {
|
|
705
|
+
return [];
|
|
478
706
|
}
|
|
479
707
|
isRoot() {
|
|
480
708
|
return this.config.isRoot ?? false;
|
|
@@ -578,7 +806,7 @@ var GroupItemCore = class extends SurveyItemCore {
|
|
|
578
806
|
}
|
|
579
807
|
};
|
|
580
808
|
var PageBreakItemCore = class extends SurveyItemCore {
|
|
581
|
-
type =
|
|
809
|
+
type = "page-break";
|
|
582
810
|
parseConfig(_rawConfig) {
|
|
583
811
|
return {};
|
|
584
812
|
}
|
|
@@ -586,16 +814,15 @@ var PageBreakItemCore = class extends SurveyItemCore {
|
|
|
586
814
|
isInteractive() {
|
|
587
815
|
return false;
|
|
588
816
|
}
|
|
589
|
-
|
|
590
|
-
return
|
|
817
|
+
getResponseSlotDefinitions() {
|
|
818
|
+
return [];
|
|
591
819
|
}
|
|
592
820
|
};
|
|
593
821
|
const builtInItemCoreRegistry = {
|
|
594
|
-
[
|
|
595
|
-
[
|
|
822
|
+
["group"]: GroupItemCore,
|
|
823
|
+
["page-break"]: PageBreakItemCore
|
|
596
824
|
};
|
|
597
825
|
const isBuiltInItemType = (itemType) => itemType in builtInItemCoreRegistry;
|
|
598
|
-
|
|
599
826
|
//#endregion
|
|
600
827
|
//#region src/survey/registry/item-registry.ts
|
|
601
828
|
/** Convert constructor registry to explicit definitions for higher-layer composition. */
|
|
@@ -633,17 +860,15 @@ function createFullRegistry(pluginRegistry) {
|
|
|
633
860
|
function createItemTypeDefinitionRegistry(pluginRegistry) {
|
|
634
861
|
return toItemTypeDefinitionRegistry(createFullRegistry(pluginRegistry));
|
|
635
862
|
}
|
|
636
|
-
|
|
637
863
|
//#endregion
|
|
638
864
|
//#region src/survey/survey-file-schema.ts
|
|
639
865
|
const CURRENT_SURVEY_SCHEMA = "https://github.com/case-framework/case-survey-toolkit/packages/survey-core/schemas/survey-schema.json";
|
|
640
|
-
|
|
641
866
|
//#endregion
|
|
642
867
|
//#region src/survey/utils/translations.ts
|
|
643
868
|
const validateLocale = (locale) => {
|
|
644
869
|
if (locale.trim() === "") throw new Error("Locale cannot be empty");
|
|
645
870
|
};
|
|
646
|
-
var SurveyItemTranslations = class {
|
|
871
|
+
var SurveyItemTranslations = class SurveyItemTranslations {
|
|
647
872
|
_translations;
|
|
648
873
|
constructor() {
|
|
649
874
|
this._translations = {};
|
|
@@ -676,6 +901,48 @@ var SurveyItemTranslations = class {
|
|
|
676
901
|
if (content) return content;
|
|
677
902
|
if (fallbackLocale) return this._translations?.[fallbackLocale]?.[contentKey];
|
|
678
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Create a deep clone that can be safely mutated before persisting with
|
|
906
|
+
* `setItemTranslations` or `updateItemTranslations`.
|
|
907
|
+
*/
|
|
908
|
+
clone() {
|
|
909
|
+
const cloned = new SurveyItemTranslations();
|
|
910
|
+
for (const locale of this.locales) {
|
|
911
|
+
const localeContent = this.getAllForLocale(locale);
|
|
912
|
+
if (!localeContent) continue;
|
|
913
|
+
cloned.setAllForLocale(locale, structuredCloneMethod(localeContent));
|
|
914
|
+
}
|
|
915
|
+
return cloned;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Remove a single content key from every locale in this translation set.
|
|
919
|
+
*/
|
|
920
|
+
removeContentKey(contentKey) {
|
|
921
|
+
for (const locale of this.locales) delete this._translations?.[locale]?.[contentKey];
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Remove every content key that is exactly the prefix or nested beneath it.
|
|
925
|
+
*/
|
|
926
|
+
removeContentKeysWithPrefix(prefix) {
|
|
927
|
+
for (const locale of this.locales) {
|
|
928
|
+
const localeContent = this._translations?.[locale];
|
|
929
|
+
if (!localeContent) continue;
|
|
930
|
+
for (const contentKey of Object.keys(localeContent)) if (contentKey === prefix || contentKey.startsWith(prefix + ".")) delete localeContent[contentKey];
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Remove empty locales in place.
|
|
935
|
+
* Returns `undefined` when no translations remain so callers can pass the
|
|
936
|
+
* result directly into `setItemTranslations` / `updateItemTranslations`.
|
|
937
|
+
*/
|
|
938
|
+
compact() {
|
|
939
|
+
for (const locale of this.locales) {
|
|
940
|
+
const localeContent = this._translations?.[locale];
|
|
941
|
+
if (!localeContent) continue;
|
|
942
|
+
if (Object.keys(localeContent).length === 0) delete this._translations?.[locale];
|
|
943
|
+
}
|
|
944
|
+
return this.locales.length > 0 ? this : void 0;
|
|
945
|
+
}
|
|
679
946
|
};
|
|
680
947
|
var SurveyTranslations = class {
|
|
681
948
|
_translations;
|
|
@@ -760,20 +1027,26 @@ var SurveyTranslations = class {
|
|
|
760
1027
|
const contentForLocale = this._translations?.[locale].itemTranslations?.[itemId];
|
|
761
1028
|
itemTranslations.setAllForLocale(locale, contentForLocale);
|
|
762
1029
|
}
|
|
763
|
-
return itemTranslations;
|
|
1030
|
+
return itemTranslations.locales.length > 0 ? itemTranslations : void 0;
|
|
764
1031
|
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Persist item translations after normalizing them:
|
|
1034
|
+
* empty keys/locales are removed automatically, and an empty translation set
|
|
1035
|
+
* clears the item's stored translations entirely.
|
|
1036
|
+
*/
|
|
765
1037
|
setItemTranslations(itemId, itemContent) {
|
|
766
|
-
itemContent?.
|
|
767
|
-
|
|
1038
|
+
const normalizedItemContent = itemContent?.clone().compact();
|
|
1039
|
+
normalizedItemContent?.locales.forEach((locale) => validateLocale(locale));
|
|
1040
|
+
if (!normalizedItemContent) {
|
|
768
1041
|
for (const locale of this.locales) if (this._translations[locale].itemTranslations?.[itemId]) delete this._translations[locale].itemTranslations?.[itemId];
|
|
769
1042
|
} else {
|
|
770
|
-
const localesInUpdate =
|
|
1043
|
+
const localesInUpdate = normalizedItemContent.locales;
|
|
771
1044
|
for (const locale of localesInUpdate) if (!this.locales.includes(locale)) this._translations[locale] = {};
|
|
772
1045
|
for (const locale of this.locales) if (localesInUpdate.includes(locale)) {
|
|
773
1046
|
if (!this._translations[locale]) this._translations[locale] = {};
|
|
774
1047
|
if (!this._translations[locale].itemTranslations) this._translations[locale].itemTranslations = {};
|
|
775
|
-
this._translations[locale].itemTranslations[itemId] =
|
|
776
|
-
} else delete this._translations[locale].itemTranslations[itemId];
|
|
1048
|
+
this._translations[locale].itemTranslations[itemId] = normalizedItemContent.getAllForLocale(locale);
|
|
1049
|
+
} else delete this._translations[locale].itemTranslations?.[itemId];
|
|
777
1050
|
}
|
|
778
1051
|
}
|
|
779
1052
|
/**
|
|
@@ -814,7 +1087,110 @@ var SurveyTranslations = class {
|
|
|
814
1087
|
for (const locale of this.locales) delete this._translations[locale].itemTranslations?.[id];
|
|
815
1088
|
}
|
|
816
1089
|
};
|
|
817
|
-
|
|
1090
|
+
//#endregion
|
|
1091
|
+
//#region src/survey/utils/content.ts
|
|
1092
|
+
let ContentType = /* @__PURE__ */ function(ContentType) {
|
|
1093
|
+
ContentType["richText"] = "richText";
|
|
1094
|
+
ContentType["plain"] = "plain";
|
|
1095
|
+
ContentType["md"] = "md";
|
|
1096
|
+
return ContentType;
|
|
1097
|
+
}({});
|
|
1098
|
+
function hasRenderableRichTextBlock(block) {
|
|
1099
|
+
switch (block.type) {
|
|
1100
|
+
case "paragraph": return block.children.length > 0;
|
|
1101
|
+
case "heading": return block.children.length > 0;
|
|
1102
|
+
case "bulletList": return block.items.some((item) => item.children.some((paragraph) => paragraph.children.length > 0));
|
|
1103
|
+
case "image": return true;
|
|
1104
|
+
case "infoBox": return true;
|
|
1105
|
+
case "separator": return true;
|
|
1106
|
+
case "blockExtension": return true;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
function hasRenderableRichTextContent(content) {
|
|
1110
|
+
return content.doc.blocks.some((block) => hasRenderableRichTextBlock(block));
|
|
1111
|
+
}
|
|
1112
|
+
function createRichTextContent(text = "") {
|
|
1113
|
+
return {
|
|
1114
|
+
type: "richText",
|
|
1115
|
+
version: 1,
|
|
1116
|
+
doc: {
|
|
1117
|
+
type: "doc",
|
|
1118
|
+
blocks: [{
|
|
1119
|
+
type: "paragraph",
|
|
1120
|
+
children: text.length > 0 ? text.split("\n").flatMap((line, index) => {
|
|
1121
|
+
const parts = [];
|
|
1122
|
+
if (index > 0) parts.push({ type: "lineBreak" });
|
|
1123
|
+
if (line.length > 0) parts.push({
|
|
1124
|
+
type: "text",
|
|
1125
|
+
text: line
|
|
1126
|
+
});
|
|
1127
|
+
return parts;
|
|
1128
|
+
}) : []
|
|
1129
|
+
}]
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
function getPlainTextFromLinkChildren(children) {
|
|
1134
|
+
return children.map((inline) => {
|
|
1135
|
+
switch (inline.type) {
|
|
1136
|
+
case "text": return inline.text;
|
|
1137
|
+
case "template": return `{${inline.key}}`;
|
|
1138
|
+
case "lineBreak": return "\n";
|
|
1139
|
+
}
|
|
1140
|
+
}).join("");
|
|
1141
|
+
}
|
|
1142
|
+
function getPlainTextFromInlineNodes(inlines) {
|
|
1143
|
+
return inlines.map((inline) => {
|
|
1144
|
+
switch (inline.type) {
|
|
1145
|
+
case "text": return inline.text;
|
|
1146
|
+
case "template": return `{${inline.key}}`;
|
|
1147
|
+
case "link": return getPlainTextFromLinkChildren(inline.children);
|
|
1148
|
+
case "lineBreak": return "\n";
|
|
1149
|
+
case "inlineExtension": return "";
|
|
1150
|
+
}
|
|
1151
|
+
}).join("");
|
|
1152
|
+
}
|
|
1153
|
+
function getPlainTextFromParagraphs(paragraphs) {
|
|
1154
|
+
return paragraphs.map((paragraph) => getPlainTextFromInlineNodes(paragraph.children)).join("\n");
|
|
1155
|
+
}
|
|
1156
|
+
function getPlainTextFromBlock(block) {
|
|
1157
|
+
switch (block.type) {
|
|
1158
|
+
case "paragraph": return getPlainTextFromInlineNodes(block.children);
|
|
1159
|
+
case "heading": return getPlainTextFromInlineNodes(block.children);
|
|
1160
|
+
case "bulletList": return block.items.map((item) => getPlainTextFromParagraphs(item.children)).join("\n");
|
|
1161
|
+
case "image": {
|
|
1162
|
+
const captionText = block.caption ? getPlainTextFromParagraphs(block.caption) : "";
|
|
1163
|
+
return [block.alt ?? "", captionText].filter(Boolean).join("\n");
|
|
1164
|
+
}
|
|
1165
|
+
case "infoBox": return [block.title ? getPlainTextFromInlineNodes(block.title) : "", getPlainTextFromParagraphs(block.children)].filter(Boolean).join("\n");
|
|
1166
|
+
case "separator": return "";
|
|
1167
|
+
case "blockExtension": return "";
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
function getPlainTextFromRichTextContent(content) {
|
|
1171
|
+
return content.doc.blocks.map((block) => getPlainTextFromBlock(block)).filter((text) => text.length > 0).join("\n").trim();
|
|
1172
|
+
}
|
|
1173
|
+
function getContentPlainText(content) {
|
|
1174
|
+
if (!content) return "";
|
|
1175
|
+
if (content.type !== "richText") return content.content ?? "";
|
|
1176
|
+
return getPlainTextFromRichTextContent(content);
|
|
1177
|
+
}
|
|
1178
|
+
function isContentEmpty(content) {
|
|
1179
|
+
if (!content) return true;
|
|
1180
|
+
if (content.type !== "richText") return content.content.trim().length === 0;
|
|
1181
|
+
return !hasRenderableRichTextContent(content);
|
|
1182
|
+
}
|
|
1183
|
+
function getAssetUsagesFromContent(content) {
|
|
1184
|
+
if (!content || content.type !== "richText") return [];
|
|
1185
|
+
const usages = [];
|
|
1186
|
+
content.doc.blocks.forEach((block, blockIndex) => {
|
|
1187
|
+
if (block.type === "image" && block.source.type === "asset") usages.push({
|
|
1188
|
+
assetId: block.source.assetId,
|
|
1189
|
+
blockIndex
|
|
1190
|
+
});
|
|
1191
|
+
});
|
|
1192
|
+
return usages;
|
|
1193
|
+
}
|
|
818
1194
|
//#endregion
|
|
819
1195
|
//#region src/survey/survey.ts
|
|
820
1196
|
var Survey = class Survey {
|
|
@@ -822,6 +1198,7 @@ var Survey = class Survey {
|
|
|
822
1198
|
metadata;
|
|
823
1199
|
surveyItems = /* @__PURE__ */ new Map();
|
|
824
1200
|
_templateValues = /* @__PURE__ */ new Map();
|
|
1201
|
+
_assets = /* @__PURE__ */ new Map();
|
|
825
1202
|
_translations;
|
|
826
1203
|
constructor(pluginRegistry) {
|
|
827
1204
|
this.pluginRegistry = pluginRegistry;
|
|
@@ -848,7 +1225,7 @@ var Survey = class Survey {
|
|
|
848
1225
|
surveyItems: [{
|
|
849
1226
|
id: generateId(),
|
|
850
1227
|
key: newKey,
|
|
851
|
-
itemType:
|
|
1228
|
+
itemType: "group",
|
|
852
1229
|
config: {
|
|
853
1230
|
isRoot: true,
|
|
854
1231
|
items: [],
|
|
@@ -861,7 +1238,7 @@ var Survey = class Survey {
|
|
|
861
1238
|
static fromJson(json, pluginRegistry) {
|
|
862
1239
|
const survey = new Survey(pluginRegistry);
|
|
863
1240
|
const rawSurvey = json;
|
|
864
|
-
if (rawSurvey.$schema !==
|
|
1241
|
+
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}`);
|
|
865
1242
|
survey.surveyItems = /* @__PURE__ */ new Map();
|
|
866
1243
|
if (!rawSurvey.surveyItems || rawSurvey.surveyItems.length === 0) throw new Error("surveyItems is required");
|
|
867
1244
|
rawSurvey.surveyItems.forEach((item) => {
|
|
@@ -869,6 +1246,7 @@ var Survey = class Survey {
|
|
|
869
1246
|
survey.surveyItems.set(surveyItem.id, surveyItem);
|
|
870
1247
|
});
|
|
871
1248
|
if (rawSurvey.templateValues) survey._templateValues = deserializeTemplateValues(rawSurvey.templateValues);
|
|
1249
|
+
if (rawSurvey.assets) survey._assets = new Map(Object.entries(rawSurvey.assets));
|
|
872
1250
|
survey._translations = new SurveyTranslations(rawSurvey.translations);
|
|
873
1251
|
if (rawSurvey.maxItemsPerPage) survey.maxItemsPerPage = rawSurvey.maxItemsPerPage;
|
|
874
1252
|
if (rawSurvey.metadata) survey.metadata = rawSurvey.metadata;
|
|
@@ -881,6 +1259,7 @@ var Survey = class Survey {
|
|
|
881
1259
|
};
|
|
882
1260
|
json.translations = this._translations?.serialize();
|
|
883
1261
|
if (this._templateValues) json.templateValues = serializeTemplateValues(this._templateValues);
|
|
1262
|
+
if (this._assets && this._assets.size > 0) json.assets = Object.fromEntries(this._assets.entries());
|
|
884
1263
|
if (this.maxItemsPerPage) json.maxItemsPerPage = this.maxItemsPerPage;
|
|
885
1264
|
if (this.metadata) json.metadata = this.metadata;
|
|
886
1265
|
return json;
|
|
@@ -960,6 +1339,52 @@ var Survey = class Survey {
|
|
|
960
1339
|
getTemplateValueKeys() {
|
|
961
1340
|
return Array.from(this._templateValues?.keys() || []);
|
|
962
1341
|
}
|
|
1342
|
+
getAsset(assetId) {
|
|
1343
|
+
const asset = this._assets?.get(assetId);
|
|
1344
|
+
return asset ? structuredCloneMethod(asset) : void 0;
|
|
1345
|
+
}
|
|
1346
|
+
setAsset(assetId, asset) {
|
|
1347
|
+
if (!this._assets) this._assets = /* @__PURE__ */ new Map();
|
|
1348
|
+
this._assets.set(assetId, structuredCloneMethod(asset));
|
|
1349
|
+
}
|
|
1350
|
+
deleteAsset(assetId) {
|
|
1351
|
+
this._assets?.delete(assetId);
|
|
1352
|
+
}
|
|
1353
|
+
hasAsset(assetId) {
|
|
1354
|
+
return this._assets?.has(assetId) ?? false;
|
|
1355
|
+
}
|
|
1356
|
+
getAssetIds() {
|
|
1357
|
+
return Array.from(this._assets?.keys() || []);
|
|
1358
|
+
}
|
|
1359
|
+
getAssets() {
|
|
1360
|
+
return new Map(Array.from(this._assets?.entries() || []).map(([assetId, asset]) => [assetId, structuredCloneMethod(asset)]));
|
|
1361
|
+
}
|
|
1362
|
+
getAssetUsages(filterAssetId) {
|
|
1363
|
+
const usages = [];
|
|
1364
|
+
const pushContentUsages = (content, usageScope, contentKey, locale, itemId) => {
|
|
1365
|
+
for (const usage of getAssetUsagesFromContent(content)) {
|
|
1366
|
+
if (filterAssetId && usage.assetId !== filterAssetId) continue;
|
|
1367
|
+
usages.push({
|
|
1368
|
+
assetId: usage.assetId,
|
|
1369
|
+
itemId,
|
|
1370
|
+
locale,
|
|
1371
|
+
contentKey,
|
|
1372
|
+
usageScope,
|
|
1373
|
+
blockIndex: usage.blockIndex
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
const serializedTranslations = this._translations?.serialize();
|
|
1378
|
+
const rootItemId = this.rootItem?.id;
|
|
1379
|
+
if (!serializedTranslations) return usages;
|
|
1380
|
+
for (const [locale, localeTranslations] of Object.entries(serializedTranslations)) {
|
|
1381
|
+
for (const [contentKey, content] of Object.entries(localeTranslations.surveyCardContent || {})) pushContentUsages(content, "surveyCard", contentKey, locale, rootItemId);
|
|
1382
|
+
for (const [contentKey, content] of Object.entries(localeTranslations.navigationContent || {})) pushContentUsages(content, "navigation", contentKey, locale, rootItemId);
|
|
1383
|
+
for (const [contentKey, content] of Object.entries(localeTranslations.validationMessages || {})) pushContentUsages(content, "validationMessages", contentKey, locale, rootItemId);
|
|
1384
|
+
for (const [itemId, itemTranslations] of Object.entries(localeTranslations.itemTranslations || {})) for (const [contentKey, content] of Object.entries(itemTranslations || {})) pushContentUsages(content, "itemTranslation", contentKey, locale, itemId);
|
|
1385
|
+
}
|
|
1386
|
+
return usages;
|
|
1387
|
+
}
|
|
963
1388
|
getAvailableResponseValueSlots(byType) {
|
|
964
1389
|
let valueRefs = {};
|
|
965
1390
|
for (const item of this.surveyItems.values()) valueRefs = {
|
|
@@ -968,6 +1393,12 @@ var Survey = class Survey {
|
|
|
968
1393
|
};
|
|
969
1394
|
return valueRefs;
|
|
970
1395
|
}
|
|
1396
|
+
getResponseSlotDefinitions() {
|
|
1397
|
+
return Array.from(this.surveyItems.values()).map((item) => ({
|
|
1398
|
+
itemId: item.id,
|
|
1399
|
+
slots: item.getResponseSlotDefinitions()
|
|
1400
|
+
}));
|
|
1401
|
+
}
|
|
971
1402
|
/**
|
|
972
1403
|
* Get all reference usages for the survey
|
|
973
1404
|
* @param forItemId - optional item id to filter usages for a specific item and its children (if not provided, all usages are returned)
|
|
@@ -981,7 +1412,7 @@ var Survey = class Survey {
|
|
|
981
1412
|
}
|
|
982
1413
|
if (this._templateValues) for (const [templateValueKey, templateValue] of this._templateValues.entries()) for (const ref of templateValue.expression?.responseVariableRefs || []) usages.push({
|
|
983
1414
|
itemId: templateValueKey,
|
|
984
|
-
usageType:
|
|
1415
|
+
usageType: "templateValues",
|
|
985
1416
|
valueReference: ref
|
|
986
1417
|
});
|
|
987
1418
|
return usages;
|
|
@@ -991,7 +1422,7 @@ var Survey = class Survey {
|
|
|
991
1422
|
return this.getItemPath(targetId).includes(ancestorId);
|
|
992
1423
|
}
|
|
993
1424
|
};
|
|
994
|
-
|
|
995
1425
|
//#endregion
|
|
996
|
-
export {
|
|
997
|
-
|
|
1426
|
+
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 };
|
|
1427
|
+
|
|
1428
|
+
//# sourceMappingURL=survey-yXdl8xkf.mjs.map
|