@case-framework/survey-core 0.3.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.
@@ -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
- 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;
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);
@@ -87,6 +108,7 @@ let ReferenceUsageType = /* @__PURE__ */ function(ReferenceUsageType) {
87
108
  ReferenceUsageType["templateValues"] = "templateValues";
88
109
  ReferenceUsageType["validations"] = "validations";
89
110
  ReferenceUsageType["disabledConditions"] = "disabledConditions";
111
+ ReferenceUsageType["prefills"] = "prefills";
90
112
  return ReferenceUsageType;
91
113
  }({});
92
114
  //#endregion
@@ -271,7 +293,7 @@ const serializeTemplateValue = (templateValue) => {
271
293
  returnType: templateValue.returnType,
272
294
  expression: templateValue.expression?.serialize()
273
295
  };
274
- if (templateValue.type === TemplateDefTypes.Date2String) json.dateFormat = templateValue.dateFormat;
296
+ if (templateValue.type === "date2string") json.dateFormat = templateValue.dateFormat;
275
297
  return json;
276
298
  };
277
299
  const serializeTemplateValues = (templateValues) => {
@@ -318,6 +340,44 @@ const NumberPrecision = {
318
340
  int: "int",
319
341
  float: "float"
320
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
+ }
321
381
  //#endregion
322
382
  //#region src/survey/items/utils.ts
323
383
  const displayConditionsFromJson = (json) => {
@@ -330,6 +390,119 @@ const disabledConditionsFromJson = (json) => {
330
390
  return { components: json.components ? Object.fromEntries(Object.entries(json.components).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0 };
331
391
  };
332
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
333
506
  //#region src/survey/items/survey-item.ts
334
507
  /**
335
508
  * Base class for survey item handlers.
@@ -344,7 +517,7 @@ var SurveyItemCore = class {
344
517
  displayConditions;
345
518
  disabledConditions;
346
519
  validations;
347
- prefillRules;
520
+ prefills;
348
521
  constructor(rawItem) {
349
522
  this._rawItem = rawItem;
350
523
  this.updateExpressions();
@@ -358,8 +531,8 @@ var SurveyItemCore = class {
358
531
  getAvailableResponseValueSlots(byType) {
359
532
  const valueRefs = {};
360
533
  for (const slot of this.getResponseSlotDefinitions()) {
361
- valueRefs[`${this.id}...${ValueReferenceMethod.get}...${slot.slotId}`] = slot.valueType;
362
- valueRefs[`${this.id}...${ValueReferenceMethod.isDefined}...${slot.slotId}`] = ValueType.boolean;
534
+ valueRefs[`${this.id}...get...${slot.slotId}`] = slot.valueType;
535
+ valueRefs[`${this.id}...isDefined...${slot.slotId}`] = ValueType.boolean;
363
536
  }
364
537
  Object.assign(valueRefs, this.getAdditionalResponseValueSlots());
365
538
  if (!byType) return valueRefs;
@@ -388,37 +561,60 @@ var SurveyItemCore = class {
388
561
  this.displayConditions = this._rawItem.displayConditions ? displayConditionsFromJson(this._rawItem.displayConditions) : void 0;
389
562
  this.disabledConditions = this._rawItem.disabledConditions ? disabledConditionsFromJson(this._rawItem.disabledConditions) : void 0;
390
563
  this.validations = this._rawItem.validations ? Object.fromEntries(Object.entries(this._rawItem.validations).map(([key, value]) => [key, Expression.deserialize(value)])) : void 0;
391
- this.prefillRules = this._rawItem.prefillRules ? this._rawItem.prefillRules.map((rule) => rule ? Expression.deserialize(rule) : void 0) : void 0;
564
+ this.prefills = this._rawItem.prefills?.map((prefill) => deserializeSurveyItemPrefill(prefill));
392
565
  }
393
566
  getReferenceUsages() {
394
567
  const usages = [];
395
568
  if (this.displayConditions) {
396
569
  for (const ref of this.displayConditions.root?.responseVariableRefs || []) usages.push({
397
570
  itemId: this.id,
398
- usageType: ReferenceUsageType.displayConditions,
571
+ usageType: "displayConditions",
399
572
  valueReference: ref
400
573
  });
401
574
  for (const [componentKey, expression] of Object.entries(this.displayConditions.components || {})) for (const ref of expression?.responseVariableRefs || []) usages.push({
402
575
  itemId: this.id,
403
576
  fullComponentKey: componentKey,
404
- usageType: ReferenceUsageType.displayConditions,
577
+ usageType: "displayConditions",
405
578
  valueReference: ref
406
579
  });
407
580
  }
408
581
  if (this.disabledConditions) for (const [componentKey, expression] of Object.entries(this.disabledConditions.components || {})) for (const ref of expression?.responseVariableRefs || []) usages.push({
409
582
  itemId: this.id,
410
583
  fullComponentKey: componentKey,
411
- usageType: ReferenceUsageType.disabledConditions,
584
+ usageType: "disabledConditions",
412
585
  valueReference: ref
413
586
  });
414
587
  if (this.validations) for (const [validationKey, expression] of Object.entries(this.validations)) for (const ref of expression?.responseVariableRefs || []) usages.push({
415
588
  itemId: this.id,
416
589
  fullComponentKey: validationKey,
417
- usageType: ReferenceUsageType.validations,
590
+ usageType: "validations",
418
591
  valueReference: ref
419
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
+ }
420
607
  return usages;
421
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
+ }
422
618
  };
423
619
  //#endregion
424
620
  //#region src/survey/items/types.ts
@@ -490,7 +686,7 @@ function moveArrayElement(arr, fromIndex, toIndex) {
490
686
  //#endregion
491
687
  //#region src/survey/registry/built-in-items.ts
492
688
  var GroupItemCore = class extends SurveyItemCore {
493
- type = ReservedSurveyItemTypes.Group;
689
+ type = "group";
494
690
  parseConfig(rawConfig) {
495
691
  const cfg = rawConfig ?? {};
496
692
  return {
@@ -610,7 +806,7 @@ var GroupItemCore = class extends SurveyItemCore {
610
806
  }
611
807
  };
612
808
  var PageBreakItemCore = class extends SurveyItemCore {
613
- type = ReservedSurveyItemTypes.PageBreak;
809
+ type = "page-break";
614
810
  parseConfig(_rawConfig) {
615
811
  return {};
616
812
  }
@@ -623,8 +819,8 @@ var PageBreakItemCore = class extends SurveyItemCore {
623
819
  }
624
820
  };
625
821
  const builtInItemCoreRegistry = {
626
- [ReservedSurveyItemTypes.Group]: GroupItemCore,
627
- [ReservedSurveyItemTypes.PageBreak]: PageBreakItemCore
822
+ ["group"]: GroupItemCore,
823
+ ["page-break"]: PageBreakItemCore
628
824
  };
629
825
  const isBuiltInItemType = (itemType) => itemType in builtInItemCoreRegistry;
630
826
  //#endregion
@@ -672,7 +868,7 @@ const CURRENT_SURVEY_SCHEMA = "https://github.com/case-framework/case-survey-too
672
868
  const validateLocale = (locale) => {
673
869
  if (locale.trim() === "") throw new Error("Locale cannot be empty");
674
870
  };
675
- var SurveyItemTranslations = class {
871
+ var SurveyItemTranslations = class SurveyItemTranslations {
676
872
  _translations;
677
873
  constructor() {
678
874
  this._translations = {};
@@ -705,6 +901,48 @@ var SurveyItemTranslations = class {
705
901
  if (content) return content;
706
902
  if (fallbackLocale) return this._translations?.[fallbackLocale]?.[contentKey];
707
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
+ }
708
946
  };
709
947
  var SurveyTranslations = class {
710
948
  _translations;
@@ -789,20 +1027,26 @@ var SurveyTranslations = class {
789
1027
  const contentForLocale = this._translations?.[locale].itemTranslations?.[itemId];
790
1028
  itemTranslations.setAllForLocale(locale, contentForLocale);
791
1029
  }
792
- return itemTranslations;
1030
+ return itemTranslations.locales.length > 0 ? itemTranslations : void 0;
793
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
+ */
794
1037
  setItemTranslations(itemId, itemContent) {
795
- itemContent?.locales.forEach((locale) => validateLocale(locale));
796
- if (!itemContent) {
1038
+ const normalizedItemContent = itemContent?.clone().compact();
1039
+ normalizedItemContent?.locales.forEach((locale) => validateLocale(locale));
1040
+ if (!normalizedItemContent) {
797
1041
  for (const locale of this.locales) if (this._translations[locale].itemTranslations?.[itemId]) delete this._translations[locale].itemTranslations?.[itemId];
798
1042
  } else {
799
- const localesInUpdate = itemContent.locales;
1043
+ const localesInUpdate = normalizedItemContent.locales;
800
1044
  for (const locale of localesInUpdate) if (!this.locales.includes(locale)) this._translations[locale] = {};
801
1045
  for (const locale of this.locales) if (localesInUpdate.includes(locale)) {
802
1046
  if (!this._translations[locale]) this._translations[locale] = {};
803
1047
  if (!this._translations[locale].itemTranslations) this._translations[locale].itemTranslations = {};
804
- this._translations[locale].itemTranslations[itemId] = itemContent.getAllForLocale(locale) ?? {};
805
- } else delete this._translations[locale].itemTranslations[itemId];
1048
+ this._translations[locale].itemTranslations[itemId] = normalizedItemContent.getAllForLocale(locale);
1049
+ } else delete this._translations[locale].itemTranslations?.[itemId];
806
1050
  }
807
1051
  }
808
1052
  /**
@@ -844,12 +1088,117 @@ var SurveyTranslations = class {
844
1088
  }
845
1089
  };
846
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
+ }
1194
+ //#endregion
847
1195
  //#region src/survey/survey.ts
848
1196
  var Survey = class Survey {
849
1197
  maxItemsPerPage;
850
1198
  metadata;
851
1199
  surveyItems = /* @__PURE__ */ new Map();
852
1200
  _templateValues = /* @__PURE__ */ new Map();
1201
+ _assets = /* @__PURE__ */ new Map();
853
1202
  _translations;
854
1203
  constructor(pluginRegistry) {
855
1204
  this.pluginRegistry = pluginRegistry;
@@ -876,7 +1225,7 @@ var Survey = class Survey {
876
1225
  surveyItems: [{
877
1226
  id: generateId(),
878
1227
  key: newKey,
879
- itemType: ReservedSurveyItemTypes.Group,
1228
+ itemType: "group",
880
1229
  config: {
881
1230
  isRoot: true,
882
1231
  items: [],
@@ -897,6 +1246,7 @@ var Survey = class Survey {
897
1246
  survey.surveyItems.set(surveyItem.id, surveyItem);
898
1247
  });
899
1248
  if (rawSurvey.templateValues) survey._templateValues = deserializeTemplateValues(rawSurvey.templateValues);
1249
+ if (rawSurvey.assets) survey._assets = new Map(Object.entries(rawSurvey.assets));
900
1250
  survey._translations = new SurveyTranslations(rawSurvey.translations);
901
1251
  if (rawSurvey.maxItemsPerPage) survey.maxItemsPerPage = rawSurvey.maxItemsPerPage;
902
1252
  if (rawSurvey.metadata) survey.metadata = rawSurvey.metadata;
@@ -909,6 +1259,7 @@ var Survey = class Survey {
909
1259
  };
910
1260
  json.translations = this._translations?.serialize();
911
1261
  if (this._templateValues) json.templateValues = serializeTemplateValues(this._templateValues);
1262
+ if (this._assets && this._assets.size > 0) json.assets = Object.fromEntries(this._assets.entries());
912
1263
  if (this.maxItemsPerPage) json.maxItemsPerPage = this.maxItemsPerPage;
913
1264
  if (this.metadata) json.metadata = this.metadata;
914
1265
  return json;
@@ -988,6 +1339,52 @@ var Survey = class Survey {
988
1339
  getTemplateValueKeys() {
989
1340
  return Array.from(this._templateValues?.keys() || []);
990
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
+ }
991
1388
  getAvailableResponseValueSlots(byType) {
992
1389
  let valueRefs = {};
993
1390
  for (const item of this.surveyItems.values()) valueRefs = {
@@ -1015,7 +1412,7 @@ var Survey = class Survey {
1015
1412
  }
1016
1413
  if (this._templateValues) for (const [templateValueKey, templateValue] of this._templateValues.entries()) for (const ref of templateValue.expression?.responseVariableRefs || []) usages.push({
1017
1414
  itemId: templateValueKey,
1018
- usageType: ReferenceUsageType.templateValues,
1415
+ usageType: "templateValues",
1019
1416
  valueReference: ref
1020
1417
  });
1021
1418
  return usages;
@@ -1026,6 +1423,6 @@ var Survey = class Survey {
1026
1423
  }
1027
1424
  };
1028
1425
  //#endregion
1029
- export { FunctionExpressionNames as A, serializeTemplateValues as C, Expression as D, ContextVariableType as E, generateCodingKey as F, generateId as I, shuffleIndices as L, ReferenceUsageType as M, ValueReference as N, ExpressionType as O, ValueReferenceMethod as P, structuredCloneMethod as R, serializeTemplateValue as S, ContextVariableExpression as T, NumberPrecision as _, createFullRegistry as a, deserializeTemplateValue as b, toItemTypeDefinitionRegistry as c, builtInItemCoreRegistry as d, isBuiltInItemType as f, DurationUnits as g, SurveyItemCore as h, validateLocale as i, ResponseVariableExpression as j, FunctionExpression as k, GroupItemCore as l, ReservedSurveyItemTypes as m, SurveyItemTranslations as n, createItemCore as o, SurveyItemKey as p, SurveyTranslations as r, createItemTypeDefinitionRegistry as s, Survey as t, PageBreakItemCore as u, ValueType as v, ConstExpression as w, deserializeTemplateValues as x, TemplateDefTypes as y };
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 };
1030
1427
 
1031
- //# sourceMappingURL=survey-DJbgFVPz.mjs.map
1428
+ //# sourceMappingURL=survey-yXdl8xkf.mjs.map