@case-framework/survey-core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -1,776 +1,8 @@
1
- import { $ as const_date, $n as responseValuesEqual, $t as CtxPFlagDateEditor, A as createItemTypeDefinitionRegistry, An as deserializeTemplateValue, At as min, B as deserializeSurveyItemPrefill, Bn as FunctionExpression, Bt as str_list_contains, C as SurveyItemTranslations, Cn as findIntroducedBlockingExpressionReferenceDiagnostics, Ct as eq, D as CURRENT_SURVEY_SCHEMA_VERSION, Dn as responseValueReferenceIds, Dt as lt, E as CURRENT_SURVEY_SCHEMA, En as replaceResponseVariableReferences, Et as in_range, F as isBuiltInItemType, Fn as ConstExpression, Ft as response_number, G as generateId, Gn as ValueReferenceMethod, Gt as ConstDateEditor, H as serializeSurveyItemPrefill, Hn as ResponseVariableExpression, Ht as AndExpressionEditor, I as getCompositionLabel, In as ContextVariableExpression, It as response_number_array, J as shuffleIndices, Jn as allocateResponseVariableName, Jt as ConstStringArrayEditor, K as resolveUniqueItemKey, Kn as ResponseSlotRegistry, Kt as ConstNumberArrayEditor, L as SurveyItemCore, Ln as ContextVariableType, Lt as response_string, M as GroupItemCore, Mn as serializeTemplateValue, Mt as response_boolean, N as PageBreakItemCore, Nn as serializeTemplateValues, Nt as response_date, O as createFullRegistry, On as DATE_FUNCTION_SIGNATURES, Ot as lte, P as builtInItemCoreRegistry, Pn as ExpressionEvaluator, Pt as response_date_array, Q as const_boolean, Qn as responseValueElements, Qt as CtxLocaleEditor, R as SurveyItemPrefillApplyMode, Rn as Expression, Rt as response_string_array, S as isContentEmpty, Sn as expressionReferenceDiagnosticKey, St as date_min, T as validateLocale, Tn as isBlockingSurveyExpressionReferenceDiagnostic, Tt as gte, U as createSeededRandom, Un as ReferenceUsageType, Ut as ConstBooleanEditor, V as prefillTargetsEqual, Vn as FunctionExpressionNames, Vt as sum, W as generateCodingKey, Wn as ValueReference, Wt as ConstDateArrayEditor, X as ReservedSurveyItemTypes, Xn as suggestResponseVariableName, Xt as CtxCustomExpressionEditor, Y as structuredCloneMethod, Yn as isValidResponseVariableName, Yt as ConstStringEditor, Z as and, Zn as invalidResponseDomainValues, Zt as CtxCustomValueEditor, _ as getAssetUsagesFromContent, _n as SURVEY_EXPRESSION_REFERENCE_DIAGNOSTIC_POLICY, _r as differenceCalendarDates, _t as date_gt, a as resolvedSlotEncoding, an as GtExpressionEditor, ar as SurveyItemResponse, at as ctx_custom_expression, b as hasRenderableRichTextBlock, bn as collectExpressionResponseReferences, bt as date_lte, c as EXPORTER_PROFILE_SCHEMA_VERSION, cn as LtExpressionEditor, cr as ValueType, ct as ctx_pflag_date, d as SlotTransformMode, dn as MinExpressionEditor, dr as addCalendarDate, dt as ctx_pflag_string, en as CtxPFlagIsDefinedEditor, er as validateResponseSlotValue, et as const_date_array, f as decodeExportColumnId, fn as OrExpressionEditor, fr as calendarDateBounds, ft as date_add_days, g as createRichTextContent, gn as SumExpressionEditor, gr as compareCalendarDates, gt as date_eq, h as ContentType, hn as StrListContainsExpressionEditor, hr as calendarDateToLocalDate, ht as date_diff, i as projectSlot, in as ExpressionEditor, ir as ResponseItem, it as const_string_array, j as toItemTypeDefinitionRegistry, jn as deserializeTemplateValues, jt as or, k as createItemCore, kn as TemplateDefTypes, kt as max, l as ExportColumnSource, ln as LteExpressionEditor, lr as assertResponseValue, lt as ctx_pflag_is_defined, m as Survey, mn as StrEqExpressionEditor, mr as calendarDatePrecision, mt as date_add_years, n as getDefaultProjectionIssues, nn as CtxPFlagStringEditor, nr as SURVEY_RESPONSE_SCHEMA_VERSION, nt as const_number_array, o as DEFAULT_TRANSFORM, on as GteExpressionEditor, or as DurationUnits, ot as ctx_custom_value, p as encodeExportColumnId, pn as ResponseVariableEditor, pr as calendarDateFromLocalDate, pt as date_add_months, q as shuffleArray, qn as RESPONSE_VARIABLE_NAME_MAX_LENGTH, qt as ConstNumberEditor, r as optionMapping, rn as EqExpressionEditor, rr as SurveyResponse, rt as const_string, s as exportSingleResponseSlot, sn as InRangeExpressionEditor, sr as NumberPrecision, st as ctx_locale, t as formatProjectedValue, tn as CtxPFlagNumEditor, tr as initValueForType, tt as const_number, u as META_COLUMN_ORDER, un as MaxExpressionEditor, ur as isResponseValue, ut as ctx_pflag_num, v as getContentPlainText, vn as analyzeSurveyExpressionReferences, vr as formatCalendarDate, vt as date_gte, w as SurveyTranslations, wn as findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories, wt as gt, x as hasRenderableRichTextContent, xn as collectSurveyExpressionLocations, xt as date_max, y as getPlainTextFromRichTextContent, yn as collectConfigValueExpressions, yr as isCalendarDate, yt as date_lt, z as SurveyItemPrefillTargetType, zn as ExpressionType, zt as str_eq } from "./projection-Bl6qCinx.mjs";
2
- import { SURVEY_EDITOR_ITEM_COLORS } from "./editor/colors.mjs";
1
+ import { $ as inspectAnnotated, A as slotPrefillSchema, B as ValueType, C as createCompositionSchema, Ct as calendarDaySchema, D as followUpBody, Dt as isCalendarDate, E as elementShape, Et as formatCalendarDate, F as richTextImageDimensionSchema, G as stringRecord, H as isResponseValue, I as richTextImageSourceSchema, J as boundField, K as annotated, L as surveyContentSchema, M as templateValueSchema, N as validationRuleSchema, O as itemEditorMetadataSchema, Ot as isCalendarDateUnit, P as jsonValueSchema, Q as extendObject, R as DurationUnits, S as conditionsShape, St as calendarDateToLocalDate, T as createSurveySchema, Tt as differenceCalendarDates, U as ownValue, V as assertResponseValue, W as setOwnValue, X as contentOwnerIds, Y as contentOwner, Z as expressionField, _ as hasRenderableRichTextBlock, _t as CALENDAR_DATE_UNITS, a as getLocalizationItems, at as expressionEditorConfigSchema, b as CURRENT_SURVEY_SCHEMA, bt as calendarDateFromLocalDate, c as listLocalizationEntries, ct as responseValueSchema, d as validateLocalizationRef, dt as remapExpression, et as ownedId, f as ContentType, ft as expressionArguments, g as getPlainTextFromRichTextContent, gt as visitExpression, h as getContentPlainText, ht as unreachable, i as getLocalizationEntryKey, it as transformAnnotated, j as slotTransformSchema, k as layoutPolicySchema, l as readLocalizationContent, lt as valueTypeSchema, m as getAssetUsagesFromContent, mt as transformExpression, n as getLocalizationCellKey, nt as remapAnnotated, o as hasLocalizationText, ot as expressionSchema, p as createRichTextContent, pt as operand, q as annotatedReferences, r as getLocalizationCoverage, rt as schemaAnnotation, s as isEmptySurveyLocalizationEntry, st as functionNameSchema, t as SURVEY_LOCALIZATION_SURFACES, tt as referenceId, u as resolveParticipantContent, ut as collectExpressionDependencies, v as hasRenderableRichTextContent, vt as addCalendarDate, w as createFlowItemSchema, wt as compareCalendarDates, x as CURRENT_SURVEY_SCHEMA_VERSION, xt as calendarDatePrecision, y as isContentEmpty, yt as calendarDateBounds, z as NumberPrecision } from "./localization-1lzNpyFX.mjs";
2
+ import { $ as shuffleArray, A as isAdmissibleInDraft, B as expressionIssues, C as surveyEventSchema, D as DefinitionError, E as hasPermittedChange, F as allocateResponseVariableName, G as responseValueElements, H as functionTypeIssues, I as isValidResponseVariableName, J as FUNCTION_SIGNATURES, K as responseValuesEqual, L as suggestResponseVariableName, M as getCompositionLabel, N as ResponseSlotRegistry, O as Survey, P as RESPONSE_VARIABLE_NAME_MAX_LENGTH, Q as resolveUniqueItemKey, R as ExpressionEvaluationError, S as itemResponseMetadataSchema, T as canWriteSelection, U as inferExpressionReturnType, V as expressionTypeIssues, W as invalidResponseDomainValues, X as generateCodingKey, Y as createSeededRandom, Z as generateId, _ as SlotTransformMode, a as optionIds, b as surveyResponseSchema, c as resolvedSlotEncoding, d as exportSingleResponseSlot, et as shuffleIndices, f as decodeExportColumnId, g as META_COLUMN_ORDER, h as ExportColumnSource, i as localizedExportSlots, j as surveyLocales, k as checkDocumentBudget, l as BooleanExportValues, m as EXPORTER_PROFILE_SCHEMA_VERSION, n as getDefaultProjectionDiagnostics, o as optionMapping, p as encodeExportColumnId, q as validateResponseSlotValue, r as getDefaultProjectionIssues, s as projectSlot, t as formatProjectedValue, tt as structuredCloneMethod, u as DEFAULT_TRANSFORM, v as SURVEY_RESPONSE_SCHEMA_VERSION, w as SurveyStateGraph, x as SurveyEventTypes, y as SurveyResponse, z as evaluateExpression } from "./projection-DrLPFroE.mjs";
3
+ import { t as strictKeyedObject } from "./keyed-object-D1yh_W5v.mjs";
3
4
  import { format } from "date-fns/format";
4
- import { enUS } from "date-fns/locale/en-US";
5
- //#region src/to_mirgrate/data_types/legacy-types.ts
6
- const isLegacyItemGroupComponent = (item) => {
7
- const items = item.items;
8
- return items !== void 0 && items.length > 0;
9
- };
10
- const isLegacySurveyGroupItem = (item) => {
11
- const items = item.items;
12
- return items !== void 0 && items.length > 0;
13
- };
14
- //#endregion
15
- //#region src/survey/responses/response-meta.ts
16
- const SurveyEventTypes = {
17
- openSurvey: "openSurvey",
18
- pageChanged: "pageChanged",
19
- responseChanged: "responseChanged",
20
- languageChanged: "languageChanged"
21
- };
22
- //#endregion
23
- //#region src/expressions/item-expression-registry.ts
24
- function getItemExpressionDefinition(definitions, expressionId) {
25
- return definitions?.find((definition) => definition.id === expressionId);
26
- }
27
- function buildItemExpression(item, definitions, expressionId, params) {
28
- const definition = getItemExpressionDefinition(definitions, expressionId);
29
- if (!definition) throw new Error(`Unknown item expression: ${expressionId}`);
30
- return definition.buildExpression(item, params);
31
- }
32
- //#endregion
33
- //#region src/engine/active-responses.ts
34
- const itemNode = (id) => JSON.stringify(["item", id]);
35
- const componentNode = (id, component) => JSON.stringify([
36
- "component",
37
- id,
38
- component
39
- ]);
40
- const slotNode = (id) => JSON.stringify(["answer", id]);
41
- function assertDeclarativeVisibility(expression) {
42
- if (!expression) return;
43
- if (expression.type === ExpressionType.ContextVariable) {
44
- if (expression.contextType === ContextVariableType.CustomExpression) throw new Error("Visibility conditions cannot use custom callback expressions; provide a serializable custom context value instead.");
45
- assertDeclarativeVisibility(expression.key);
46
- }
47
- if ("arguments" in expression) for (const argument of expression.arguments ?? []) assertDeclarativeVisibility(argument);
48
- }
49
- /** Compile the dependencies once; each evaluation reads an immutable logical session snapshot. */
50
- var ActiveResponsePlan = class {
51
- survey;
52
- parents = /* @__PURE__ */ new Map();
53
- components = /* @__PURE__ */ new Map();
54
- slots;
55
- constructor(survey) {
56
- this.survey = survey;
57
- this.slots = survey.getResponseSlotRegistry();
58
- for (const item of survey.surveyItems.values()) {
59
- if (item.type === "group") for (const child of item.items ?? []) this.parents.set(child, item.id);
60
- for (const { slotId, componentId } of item.getConditionalResponseSlots()) this.components.set(slotId, [...this.components.get(slotId) ?? [], componentId]);
61
- }
62
- const graph = /* @__PURE__ */ new Map();
63
- const labels = /* @__PURE__ */ new Map();
64
- const refs = (expression) => expression?.responseVariableRefs.map((ref) => slotNode(ref.slotId)) ?? [];
65
- for (const item of survey.surveyItems.values()) {
66
- for (const expression of [item.displayConditions?.root, ...Object.values(item.displayConditions?.components ?? {})]) assertDeclarativeVisibility(expression?.serialize());
67
- const label = item.metadata?.itemLabel?.trim() || item.id;
68
- const parent = this.parents.get(item.id);
69
- graph.set(itemNode(item.id), [...parent ? [itemNode(parent)] : [], ...refs(item.displayConditions?.root)]);
70
- labels.set(itemNode(item.id), `${label} visibility`);
71
- for (const [component, expression] of Object.entries(item.displayConditions?.components ?? {})) {
72
- graph.set(componentNode(item.id, component), [itemNode(item.id), ...refs(expression)]);
73
- labels.set(componentNode(item.id, component), `${label} (${component}) visibility`);
74
- }
75
- for (const slot of item.getResponseSlotDefinitions()) {
76
- const activation = item.getActiveResponseDependencies(slot.slotId);
77
- graph.set(slotNode(slot.slotId), [
78
- itemNode(item.id),
79
- ...(this.components.get(slot.slotId) ?? []).map((id) => componentNode(item.id, id)),
80
- ...item.getComputedResponseDependencies(slot.slotId).map(slotNode),
81
- ...activation.slotIds.map(slotNode),
82
- ...activation.componentIds.map((id) => componentNode(item.id, id))
83
- ]);
84
- labels.set(slotNode(slot.slotId), `${label} (${slot.variableName || slot.slotId})`);
85
- }
86
- }
87
- const done = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), path = [];
88
- const visit = (node) => {
89
- if (visiting.has(node)) throw new Error(`Cyclic response/visibility dependency: ${[...path.slice(path.indexOf(node)), node].map((id) => labels.get(id) ?? id).join(" → ")}`);
90
- if (done.has(node)) return;
91
- visiting.add(node);
92
- path.push(node);
93
- for (const dependency of graph.get(node) ?? []) visit(dependency);
94
- path.pop();
95
- visiting.delete(node);
96
- done.add(node);
97
- };
98
- for (const node of graph.keys()) visit(node);
99
- }
100
- evaluate(responses, context) {
101
- const diagnostics = [];
102
- const cache = /* @__PURE__ */ new Map();
103
- const visiting = /* @__PURE__ */ new Set();
104
- const resolve = (key, compute) => {
105
- if (cache.has(key)) return cache.get(key);
106
- if (visiting.has(key)) throw new Error(`Cyclic response/visibility dependency: ${key}`);
107
- visiting.add(key);
108
- try {
109
- const value = compute();
110
- cache.set(key, value);
111
- return value;
112
- } finally {
113
- visiting.delete(key);
114
- }
115
- };
116
- const condition = (itemId, expression, componentId) => {
117
- if (!expression) return true;
118
- const value = evaluator.eval(expression);
119
- if (value !== void 0 && value.type !== ValueType.boolean) diagnostics.push({
120
- code: "non-boolean-visibility-result",
121
- itemId,
122
- ...componentId === void 0 ? {} : { componentId },
123
- actualType: value.type
124
- });
125
- return value?.type === ValueType.boolean && value.value;
126
- };
127
- const isItemVisible = (id) => resolve(itemNode(id), () => {
128
- const item = this.survey.surveyItems.get(id);
129
- const parent = this.parents.get(id);
130
- return !!item && (id === this.survey.rootItem?.id || !!parent && isItemVisible(parent)) && condition(id, item.displayConditions?.root);
131
- });
132
- const isComponentVisible = (id, component) => resolve(componentNode(id, component), () => isItemVisible(id) && condition(id, this.survey.surveyItems.get(id)?.displayConditions?.components?.[component], component));
133
- const getAnswer = (slotId) => resolve(slotNode(slotId), () => {
134
- const owner = this.slots.get(slotId);
135
- if (!owner || !isItemVisible(owner.itemId) || !(this.components.get(slotId) ?? []).every((id) => isComponentVisible(owner.itemId, id))) return void 0;
136
- const item = this.survey.surveyItems.get(owner.itemId);
137
- if (!owner.computed) return item.getActiveResponseValue(slotId, responses[owner.itemId]?.response?.get(slotId), (id) => isComponentVisible(owner.itemId, id), getAnswer);
138
- const sources = new ResponseItem();
139
- for (const id of item.getComputedResponseDependencies(slotId)) sources.setSlotValue(id, getAnswer(id));
140
- return item.getComputedResponseValue(slotId, sources, (id) => isComponentVisible(owner.itemId, id));
141
- });
142
- const evaluator = new ExpressionEvaluator({
143
- responses,
144
- surveyContext: context,
145
- resolveResponseValue: getAnswer
146
- });
147
- const getItemResponse = (id) => {
148
- const response = new ResponseItem();
149
- for (const slot of this.survey.surveyItems.get(id)?.getResponseSlotDefinitions() ?? []) if (!slot.computed) response.setSlotValue(slot.slotId, getAnswer(slot.slotId));
150
- return response.isEmpty() ? void 0 : response;
151
- };
152
- return {
153
- evaluator,
154
- getAnswer,
155
- getItemResponse,
156
- isItemVisible,
157
- isComponentVisible,
158
- getDiagnostics: () => diagnostics.map((diagnostic) => ({ ...diagnostic }))
159
- };
160
- }
161
- };
162
- //#endregion
163
- //#region src/engine/engine.ts
164
- var SurveyEngineCore = class SurveyEngineCore {
165
- activeResponsePlan;
166
- activeState;
167
- responseDiagnostics = /* @__PURE__ */ new Map();
168
- surveyDef;
169
- renderedSurveyTree;
170
- renderedItemsCache;
171
- context;
172
- locale;
173
- sessionSeedValue;
174
- responses;
175
- prefills;
176
- _events;
177
- _openedAt;
178
- dateLocales;
179
- prefillEnvironment;
180
- cache;
181
- constructor(survey, context, prefills, dateLocales, options) {
182
- this._openedAt = this._getTimestamp();
183
- this.sessionSeedValue = generateId();
184
- this.surveyDef = survey;
185
- const { issues: configurationIssues, plan } = SurveyEngineCore.inspectConfiguration(survey);
186
- if (configurationIssues.length) throw new Error(configurationIssues.join("\n"));
187
- this.activeResponsePlan = plan;
188
- this.context = context ? context : { locale: "en" };
189
- this.locale = this.context.locale;
190
- if (prefills) {
191
- const registry = survey.getResponseSlotRegistry();
192
- this.prefills = Object.create(null);
193
- for (const [slotId, value] of Object.entries(prefills)) {
194
- const owner = registry.get(slotId);
195
- if (!owner || owner.computed) throw new Error(`Cannot restore unknown or computed response slot "${slotId}"`);
196
- const item = survey.surveyItems.get(owner.itemId);
197
- (this.prefills[owner.itemId] ??= new SurveyItemResponse({
198
- itemId: item.id,
199
- itemType: item.type
200
- }, new ResponseItem())).response.setSlotValue(slotId, value);
201
- }
202
- }
203
- this.prefillEnvironment = options?.prefillEnvironment;
204
- this.dateLocales = dateLocales || [{
205
- code: "en",
206
- locale: enUS
207
- }];
208
- this.responses = this.initResponseObject(this.surveyDef.surveyItems);
209
- this.initCache();
210
- if (options?.applyConfiguredPrefills !== false) this.applyConfiguredPrefills();
211
- this.evalExpressions();
212
- if (!survey.rootItem) throw new Error("constructor: root item not found");
213
- this.renderedSurveyTree = this.renderGroup(survey.rootItem);
214
- this._events = [{
215
- type: SurveyEventTypes.openSurvey,
216
- timestamp: this._getTimestamp(),
217
- value: this.context.locale
218
- }];
219
- }
220
- setContext(context) {
221
- this.updateContext(context);
222
- }
223
- getDateLocales() {
224
- return this.dateLocales.slice();
225
- }
226
- getCurrentDateLocale() {
227
- const found = this.dateLocales.find((dl) => dl.code === this.locale);
228
- if (!found) {
229
- console.warn(`Locale '${this.locale}' is not available. Using default locale.`);
230
- if (this.dateLocales.length > 0) return this.dateLocales[0].locale;
231
- return enUS;
232
- }
233
- return found?.locale;
234
- }
235
- updateContext(context) {
236
- const localeChanged = this.locale !== context.locale;
237
- this.context = context;
238
- this.locale = context.locale;
239
- if (localeChanged) this._registerSurveyEvent({
240
- type: SurveyEventTypes.languageChanged,
241
- timestamp: this._getTimestamp(),
242
- value: context.locale
243
- });
244
- this.evalExpressions();
245
- this.reRenderSurveyTree();
246
- }
247
- /** Change the presentation language without losing participant/custom context. */
248
- updateLocale(locale) {
249
- if (this.context.locale === locale) return false;
250
- this.updateContext({
251
- ...this.context,
252
- locale
253
- });
254
- return true;
255
- }
256
- setResponse(targetKey, response) {
257
- const target = this.getResponseItem(targetKey);
258
- if (!target) throw new Error("setResponse: target not found for key: " + targetKey);
259
- const item = this.surveyDef.surveyItems.get(targetKey);
260
- const previousSlots = structuredClone(target.response?.serialize()?.slots ?? {});
261
- const normalized = item.normalizeResponse(response);
262
- target.response = normalized.response;
263
- this.responseDiagnostics.set(targetKey, normalized.diagnostics ?? []);
264
- const nextSlots = target.response?.serialize()?.slots ?? {};
265
- const timestamp = this._getTimestamp();
266
- const comparable = (value) => value === void 0 ? void 0 : JSON.stringify(value, Object.keys(value).sort());
267
- for (const slotId of /* @__PURE__ */ new Set([...Object.keys(previousSlots), ...Object.keys(nextSlots)])) {
268
- if (comparable(previousSlots[slotId]) === comparable(nextSlots[slotId])) continue;
269
- this._registerSurveyEvent({
270
- type: SurveyEventTypes.responseChanged,
271
- timestamp,
272
- slotId,
273
- operation: nextSlots[slotId] === void 0 ? "clear" : "set"
274
- });
275
- }
276
- this.evalExpressions();
277
- this.reRenderSurveyTree();
278
- }
279
- get openedAt() {
280
- return this._openedAt;
281
- }
282
- /** Invalid visibility results in the current evaluation; unanswered conditions are omitted. */
283
- getEvaluationDiagnostics() {
284
- return this.activeEvaluation().getDiagnostics();
285
- }
286
- /** Diagnostics from the latest initialization or response update for an item. */
287
- getResponseDiagnostics(itemId) {
288
- return (this.responseDiagnostics.get(itemId) ?? []).map((diagnostic) => ({ ...diagnostic }));
289
- }
290
- get sessionSeed() {
291
- return this.sessionSeedValue;
292
- }
293
- get survey() {
294
- return this.surveyDef;
295
- }
296
- getSurveyPages(size) {
297
- const renderedSurvey = flattenTree(this.renderedSurveyTree);
298
- const pages = new Array();
299
- if (!size) size = "large";
300
- let currentPage = [];
301
- renderedSurvey.forEach((item) => {
302
- if (item.type === "page-break") {
303
- if (currentPage.length > 0) {
304
- pages.push([...currentPage]);
305
- currentPage = [];
306
- }
307
- return;
308
- }
309
- currentPage.push(item);
310
- if (!this.surveyDef.maxItemsPerPage) return;
311
- let max = 0;
312
- switch (size) {
313
- case "large":
314
- max = this.surveyDef.maxItemsPerPage.large;
315
- break;
316
- case "small": max = this.surveyDef.maxItemsPerPage.small;
317
- }
318
- if (currentPage.length >= max) {
319
- pages.push([...currentPage]);
320
- currentPage = [];
321
- }
322
- });
323
- if (currentPage.length > 0) pages.push([...currentPage]);
324
- return pages;
325
- }
326
- /** Read-only projection, with stable identity until the engine renders a new tree. */
327
- getRenderedSurveyItems() {
328
- if (this.renderedItemsCache?.tree !== this.renderedSurveyTree) this.renderedItemsCache = {
329
- tree: this.renderedSurveyTree,
330
- items: Object.freeze(flattenTree(this.renderedSurveyTree).map((item) => Object.freeze(item)))
331
- };
332
- return this.renderedItemsCache.items;
333
- }
334
- getRenderedSurveyItem(itemId) {
335
- const item = this.getRenderedSurveyItems().find((item) => item.id === itemId);
336
- return item ? { ...item } : void 0;
337
- }
338
- onPageChanged(pageIndex) {
339
- this._registerSurveyEvent({
340
- type: SurveyEventTypes.pageChanged,
341
- timestamp: this._getTimestamp(),
342
- value: pageIndex
343
- });
344
- }
345
- getEvents() {
346
- return this._events.slice();
347
- }
348
- /** Current session answers, including retained answers to hidden items. */
349
- getResponses() {
350
- return this.collectResponses(true);
351
- }
352
- /** Answers to currently visible items and response slots on every page; does not erase hidden session answers. */
353
- getSubmissionResponses() {
354
- return this.collectResponses(false);
355
- }
356
- /** Flat submission with events and item metadata kept separate from answer identity. */
357
- getSurveyResponse(key, includeHidden = false) {
358
- const response = new SurveyResponse(key);
359
- for (const item of this.collectResponses(includeHidden)) response.addItemResponse(item);
360
- response.evaluationContext = structuredClone({
361
- locale: this.context.locale,
362
- participantFlags: this.context.participantFlags
363
- });
364
- response.events = structuredClone(this._events);
365
- response.context = this.context.customValues && structuredClone(this.context.customValues);
366
- return response;
367
- }
368
- getAnswer(slotId) {
369
- const owner = this.surveyDef.getResponseSlotRegistry().get(slotId);
370
- if (!owner) throw new Error(`Unknown response slot "${slotId}"`);
371
- return owner.computed ? this.getComputedResponseValue(slotId) : this.responses[owner.itemId]?.response?.get(slotId);
372
- }
373
- setAnswer(slotId, value) {
374
- const owner = this.surveyDef.getResponseSlotRegistry().get(slotId);
375
- if (!owner || owner.computed) throw new Error(`Cannot write unknown or computed response slot "${slotId}"`);
376
- const response = this.responses[owner.itemId]?.response?.clone() ?? new ResponseItem();
377
- response.setSlotValue(slotId, value);
378
- this.setResponse(owner.itemId, response);
379
- }
380
- collectResponses(includeHidden) {
381
- const renderedPositions = /* @__PURE__ */ new Map();
382
- flattenTree(this.renderedSurveyTree).filter((item) => item.type !== "page-break").forEach((item, index) => {
383
- if (this.responses[item.id]) renderedPositions.set(item.id, index);
384
- });
385
- const active = includeHidden ? void 0 : this.activeEvaluation();
386
- const responses = [];
387
- this.surveyDef.surveyItems.forEach((item, itemId) => {
388
- if (!item.isInteractive()) return;
389
- const response = this.getResponseItem(itemId);
390
- if (!response) return;
391
- const position = renderedPositions.get(itemId);
392
- response.meta = position === void 0 ? void 0 : { position };
393
- if (!includeHidden && position === void 0) return;
394
- if (includeHidden) {
395
- responses.push(response);
396
- return;
397
- }
398
- const submission = new SurveyItemResponse(response, active.getItemResponse(itemId));
399
- submission.meta = response.meta && { ...response.meta };
400
- responses.push(submission);
401
- });
402
- return responses;
403
- }
404
- /** Author and intrinsic validation failures, restricted to currently rendered items. */
405
- getInvalidResponseItems(itemIds) {
406
- const rendered = new Set(this.getRenderedSurveyItems().map((item) => item.id));
407
- return [...new Set(itemIds ?? rendered)].filter((id) => rendered.has(id) && Object.values(this.getValidationValues(id) ?? {}).some((valid) => !valid));
408
- }
409
- getIntrinsicValidationValues(itemKey) {
410
- const active = this.activeEvaluation();
411
- if (!active.isItemVisible(itemKey)) return {};
412
- return { ...this.surveyDef.surveyItems.get(itemKey)?.getResponseValidationValues(active.getItemResponse(itemKey), (id) => active.isComponentVisible(itemKey, id), (expression) => active.evaluator.eval(Expression.deserialize(expression))) };
413
- }
414
- getAuthorValidationValues(itemKey) {
415
- if (!this.activeEvaluation().isItemVisible(itemKey)) return {};
416
- return Object.fromEntries(Object.entries(this.cache.validations[itemKey] ?? {}).map(([key, validation]) => [key, validation.result]));
417
- }
418
- getDisplayConditionValue(itemKey, componentKey) {
419
- if (componentKey) return this.cache.displayConditions[itemKey]?.components?.[componentKey]?.result;
420
- return this.cache.displayConditions[itemKey]?.root?.result;
421
- }
422
- getDisabledConditionValue(itemKey, componentKey) {
423
- return this.cache.disabledConditions[itemKey]?.components?.[componentKey]?.result;
424
- }
425
- getTemplateValue(templateValueKey) {
426
- return this.cache.templateValues[templateValueKey]?.value;
427
- }
428
- getValidationValues(itemKey) {
429
- const result = this.getIntrinsicValidationValues(itemKey);
430
- for (const [key, valid] of Object.entries(this.getAuthorValidationValues(itemKey))) result[key] = valid && (result[key] ?? true);
431
- return Object.keys(result).length ? result : void 0;
432
- }
433
- initCache() {
434
- this.cache = {
435
- validations: {},
436
- displayConditions: {},
437
- templateValues: {},
438
- disabledConditions: {}
439
- };
440
- this.surveyDef.getTemplateValueKeys().forEach((templateKey) => {
441
- this.cache.templateValues[templateKey] = {
442
- value: void 0,
443
- templateDef: this.surveyDef.getTemplateValue(templateKey)
444
- };
445
- });
446
- this.surveyDef.surveyItems.forEach((item, itemId) => {
447
- if (item.validations && Object.keys(item.validations).length > 0) {
448
- this.cache.validations[itemId] = {};
449
- Object.keys(item.validations).forEach((validationKey) => {
450
- const valExp = item.validations[validationKey];
451
- if (!valExp) {
452
- console.warn("initCache: validation expression not found: " + itemId + " " + validationKey);
453
- return;
454
- }
455
- this.cache.validations[itemId][validationKey] = {
456
- expression: valExp,
457
- result: false
458
- };
459
- });
460
- }
461
- if (item.displayConditions !== void 0 && (item.displayConditions.root || item.displayConditions.components)) {
462
- this.cache.displayConditions[itemId] = {};
463
- if (item.displayConditions.root) this.cache.displayConditions[itemId].root = {
464
- expression: item.displayConditions.root,
465
- result: false
466
- };
467
- if (item.displayConditions.components) {
468
- this.cache.displayConditions[itemId].components = {};
469
- Object.keys(item.displayConditions.components).forEach((componentKey) => {
470
- const compExp = item.displayConditions?.components?.[componentKey];
471
- if (!compExp) {
472
- console.warn("initCache: display condition component expression not found: " + itemId + " " + componentKey);
473
- return;
474
- }
475
- this.cache.displayConditions[itemId].components[componentKey] = {
476
- expression: compExp,
477
- result: false
478
- };
479
- });
480
- }
481
- }
482
- if (item.disabledConditions !== void 0 && item.disabledConditions.components !== void 0) {
483
- this.cache.disabledConditions[itemId] = { components: {} };
484
- Object.keys(item.disabledConditions.components).forEach((componentKey) => {
485
- const compExp = item.disabledConditions?.components?.[componentKey];
486
- if (!compExp) {
487
- console.warn("initCache: disabled condition component expression not found: " + itemId + " " + componentKey);
488
- return;
489
- }
490
- this.cache.disabledConditions[itemId].components[componentKey] = {
491
- expression: compExp,
492
- result: false
493
- };
494
- });
495
- }
496
- });
497
- }
498
- initResponseObject(items) {
499
- const respGroup = {};
500
- items.forEach((item, itemId) => {
501
- if (!item.isInteractive()) return;
502
- else {
503
- const prefill = this.prefills?.[itemId];
504
- const applyPrefill = prefill && prefill.itemType === item.type;
505
- const normalized = item.normalizeResponse(applyPrefill ? prefill.response : void 0);
506
- this.responseDiagnostics.set(itemId, normalized.diagnostics ?? []);
507
- respGroup[itemId] = new SurveyItemResponse({
508
- itemId,
509
- itemType: item.type
510
- }, normalized.response);
511
- }
512
- });
513
- return respGroup;
514
- }
515
- applyConfiguredPrefills() {
516
- const maxPasses = Math.max(this.surveyDef.surveyItems.size, 1);
517
- for (let pass = 0; pass < maxPasses; pass++) {
518
- this.evalTemplateValues(this.createEvaluator());
519
- let changedInPass = false;
520
- this.surveyDef.surveyItems.forEach((item) => {
521
- if (!item.isInteractive()) return;
522
- changedInPass = this.applyConfiguredPrefillsForItem(item) || changedInPass;
523
- });
524
- if (!changedInPass) break;
525
- }
526
- this.evalTemplateValues(this.createEvaluator());
527
- }
528
- applyConfiguredPrefillsForItem(item) {
529
- const responseItem = this.responses[item.id];
530
- if (!responseItem) return false;
531
- let changed = false;
532
- for (const prefill of item.prefills ?? []) {
533
- const evalEngine = this.createEvaluator();
534
- const targetSlot = item.resolvePrefillTarget(prefill.target);
535
- if (!targetSlot) continue;
536
- if ((prefill.apply ?? SurveyItemPrefillApplyMode.ifEmpty) === SurveyItemPrefillApplyMode.ifEmpty && responseItem.response?.get(targetSlot.slotId) !== void 0) continue;
537
- if (!this.shouldApplyConfiguredPrefill(prefill, evalEngine)) continue;
538
- const resolvedValue = this.resolveConfiguredPrefillValue(prefill, targetSlot, evalEngine);
539
- if (!resolvedValue) continue;
540
- const normalizedValue = item.normalizePrefillValue(prefill, targetSlot, resolvedValue);
541
- if (!normalizedValue) continue;
542
- const nextResponse = responseItem.response?.clone() ?? new ResponseItem();
543
- nextResponse.setSlotValue(targetSlot.slotId, normalizedValue);
544
- const normalized = item.normalizeResponse(nextResponse);
545
- this.responseDiagnostics.set(item.id, normalized.diagnostics ?? []);
546
- const next = normalized.response?.isEmpty() ? void 0 : normalized.response;
547
- if (JSON.stringify(responseItem.response?.serialize()) !== JSON.stringify(next?.serialize())) changed = true;
548
- responseItem.response = next;
549
- }
550
- return changed;
551
- }
552
- shouldApplyConfiguredPrefill(prefill, evalEngine) {
553
- if (!prefill.when) return true;
554
- const resolved = evalEngine.eval(prefill.when);
555
- return resolved?.type === ValueType.boolean && resolved.value === true;
556
- }
557
- resolveConfiguredPrefillValue(prefill, _target, evalEngine) {
558
- switch (prefill.source.type) {
559
- case "static": return prefill.source.value;
560
- case "expression": return evalEngine.eval(prefill.source.expression);
561
- case "templateValue": return this.getTemplateValue(prefill.source.key);
562
- case "previousResponse": {
563
- if (!this.prefillEnvironment?.getPreviousResponse) return;
564
- const resolved = this.prefillEnvironment.getPreviousResponse({
565
- surveyKey: prefill.source.ref.surveyKey,
566
- slotId: prefill.source.ref.slotId
567
- });
568
- if (!resolved) return;
569
- return resolved;
570
- }
571
- default: return;
572
- }
573
- }
574
- shouldRender(itemId, componentId) {
575
- const displayConditionResult = this.getDisplayConditionValue(itemId, componentId);
576
- if (displayConditionResult !== void 0) return displayConditionResult;
577
- return true;
578
- }
579
- sequentialRender(groupDef) {
580
- const newItems = [];
581
- for (const itemId of groupDef.items || []) {
582
- if (!this.shouldRender(itemId)) continue;
583
- const itemDef = this.surveyDef.surveyItems.get(itemId);
584
- if (!itemDef) {
585
- console.warn("sequentialRender: item not found: " + itemId);
586
- continue;
587
- }
588
- if (itemDef.type === "group") {
589
- newItems.push(this.renderGroup(itemDef));
590
- continue;
591
- }
592
- newItems.push({
593
- id: itemId,
594
- type: itemDef.type
595
- });
596
- }
597
- return {
598
- id: groupDef.id,
599
- type: "group",
600
- items: newItems
601
- };
602
- }
603
- randomizedItemRender(groupDef, parent) {
604
- const newItems = parent.items?.filter((rItem) => this.shouldRender(rItem.id)) || [];
605
- const itemIds = groupDef.items || [];
606
- const shuffledIndices = shuffleIndices(itemIds.length);
607
- for (const index of shuffledIndices) {
608
- const itemId = itemIds[index];
609
- if (parent.items?.find((rItem) => rItem.id === itemId)) continue;
610
- if (!this.shouldRender(itemId)) continue;
611
- const itemDef = this.surveyDef.surveyItems.get(itemId);
612
- if (!itemDef) {
613
- console.warn("randomizedItemRender: item not found: " + itemId);
614
- continue;
615
- }
616
- if (itemDef.type === "group") {
617
- newItems.push(this.renderGroup(itemDef, parent));
618
- continue;
619
- }
620
- newItems.push({
621
- id: itemId,
622
- type: itemDef.type
623
- });
624
- }
625
- return {
626
- id: groupDef.id,
627
- type: "group",
628
- items: newItems
629
- };
630
- }
631
- renderGroup(groupDef, parent) {
632
- if (!this.activeEvaluation().isItemVisible(groupDef.id)) return {
633
- id: groupDef.id,
634
- type: groupDef.type,
635
- items: []
636
- };
637
- if (!parent) parent = {
638
- id: groupDef.id,
639
- type: "group",
640
- items: []
641
- };
642
- if (groupDef.config.shuffleItems) return this.randomizedItemRender(groupDef, parent);
643
- return this.sequentialRender(groupDef);
644
- }
645
- reRenderSurveyTree() {
646
- if (!this.surveyDef.rootItem) throw new Error("reRenderSurveyTree: root item not found");
647
- this.renderedSurveyTree = this.renderGroup(this.surveyDef.rootItem);
648
- }
649
- _registerSurveyEvent(event) {
650
- this._events.push(event);
651
- }
652
- _getTimestamp() {
653
- return Math.floor(Date.now() / 1e3);
654
- }
655
- getResponseItem(itemId) {
656
- return this.responses[itemId];
657
- }
658
- /** Resolve computed references against current answers and conditions, with cycle detection. */
659
- getComputedResponseValue(slotId) {
660
- return this.activeEvaluation().getAnswer(slotId);
661
- }
662
- /** Evaluate against the same current responses/context used by conditions and prefills. */
663
- evaluateExpression(expression) {
664
- return this.activeEvaluation().evaluator.eval(Expression.deserialize(expression));
665
- }
666
- activeEvaluation() {
667
- return this.activeState ??= this.activeResponsePlan.evaluate(this.responses, this.context);
668
- }
669
- createEvaluator() {
670
- return this.activeResponsePlan.evaluate(this.responses, this.context).evaluator;
671
- }
672
- /** Active answer used by conditions and scoring; hidden session values remain in getAnswer(). */
673
- getActiveAnswer(slotId) {
674
- return this.activeEvaluation().getAnswer(slotId);
675
- }
676
- /** Validate static runtime contracts without constructing a response session. */
677
- static getConfigurationIssues(survey) {
678
- return SurveyEngineCore.inspectConfiguration(survey).issues;
679
- }
680
- static inspectConfiguration(survey) {
681
- const issues = [...survey.surveyItems.values()].flatMap((item) => item.getExecutionConfigurationIssues().map((issue) => `${(item.metadata?.itemLabel ?? "")?.trim() || item.id}: ${issue}`));
682
- let plan;
683
- try {
684
- plan = new ActiveResponsePlan(survey);
685
- } catch (error) {
686
- issues.push(error instanceof Error ? error.message : String(error));
687
- }
688
- issues.push(...survey.getResponseSlotRegistry().diagnostics.map((diagnostic) => diagnostic.message));
689
- issues.push(...survey.getBlockingExpressionIssues());
690
- return {
691
- issues,
692
- plan
693
- };
694
- }
695
- evalExpressions() {
696
- this.activeState = this.activeResponsePlan.evaluate(this.responses, this.context);
697
- const evalEngine = this.activeState.evaluator;
698
- this.evalTemplateValues(evalEngine);
699
- this.evalDisplayConditions();
700
- this.evalDisableConditions(evalEngine);
701
- this.evalValidations(evalEngine);
702
- }
703
- evalTemplateValues(evalEngine) {
704
- Object.keys(this.cache.templateValues).forEach((templateValueKey) => {
705
- const templateValue = this.cache.templateValues[templateValueKey];
706
- this.cache.templateValues[templateValueKey].value = void 0;
707
- if (!templateValue.templateDef?.expression) {
708
- console.warn("evalTemplateValues: template value expression not found: " + templateValueKey);
709
- return;
710
- }
711
- let evaluatedValue = evalEngine.eval(templateValue.templateDef.expression);
712
- if (evaluatedValue === void 0) {
713
- console.warn("evalTemplateValues: template value expression returned undefined: " + templateValueKey);
714
- return;
715
- }
716
- switch (templateValue.templateDef.type) {
717
- case "date2string":
718
- if (evaluatedValue.type !== ValueType.date) {
719
- console.warn("evalTemplateValues: template value expression returned non-date value: " + templateValueKey);
720
- return;
721
- }
722
- evaluatedValue = {
723
- type: ValueType.string,
724
- value: formatCalendarDate(evaluatedValue.value, templateValue.templateDef.dateFormat)
725
- };
726
- }
727
- this.cache.templateValues[templateValueKey].value = evaluatedValue;
728
- });
729
- }
730
- evalDisplayConditions() {
731
- const active = this.activeEvaluation();
732
- for (const [itemId, conditions] of Object.entries(this.cache.displayConditions)) {
733
- if (conditions.root) conditions.root.result = active.isItemVisible(itemId);
734
- for (const [id, condition] of Object.entries(conditions.components ?? {})) condition.result = active.isComponentVisible(itemId, id);
735
- }
736
- }
737
- evalDisableConditions(evalEngine) {
738
- Object.keys(this.cache.disabledConditions).forEach((itemKey) => {
739
- const disableCondition = this.cache.disabledConditions[itemKey];
740
- if (disableCondition.components) Object.keys(disableCondition.components).forEach((componentKey) => {
741
- const resolvedValue = evalEngine.eval(disableCondition.components[componentKey].expression);
742
- if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
743
- console.warn("evalDisableConditions: disable condition component expression returned undefined: " + itemKey + "." + componentKey);
744
- return;
745
- }
746
- this.cache.disabledConditions[itemKey].components[componentKey].result = resolvedValue.value;
747
- });
748
- });
749
- }
750
- evalValidations(evalEngine) {
751
- Object.keys(this.cache.validations).forEach((itemId) => {
752
- const validation = this.cache.validations[itemId];
753
- Object.keys(validation).forEach((validationKey) => {
754
- const resolvedValue = evalEngine.eval(validation[validationKey].expression);
755
- if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
756
- console.warn("evalValidations: validation expression returned undefined: " + itemId + "." + validationKey);
757
- this.cache.validations[itemId][validationKey].result = false;
758
- return;
759
- }
760
- this.cache.validations[itemId][validationKey].result = resolvedValue.value;
761
- });
762
- });
763
- }
764
- };
765
- const flattenTree = (itemTree) => {
766
- const flatTree = new Array();
767
- itemTree.items?.forEach((item) => {
768
- if (item.type === "group") flatTree.push(...flattenTree(item));
769
- else flatTree.push({ ...item });
770
- });
771
- return flatTree;
772
- };
773
- //#endregion
5
+ import { z } from "zod";
774
6
  //#region src/json-patch.ts
775
7
  var JsonPatchError = class extends Error {
776
8
  code;
@@ -956,15 +188,1551 @@ const applyJsonPatch = (document, operations) => {
956
188
  return patchedDocument;
957
189
  };
958
190
  //#endregion
959
- //#region src/response-exporter/validate-configuration.ts
960
- /** Allow incomplete authoring drafts, but never export invalid item coding paths. */
961
- function validateExportConfiguration(survey, registry = survey.getResponseSlotRegistry()) {
962
- const issues = [...survey.surveyItems.values()].flatMap((item) => item.getExecutionConfigurationIssues().map((issue) => `${(item.metadata?.itemLabel ?? "")?.trim() || item.id}: ${issue}`));
963
- issues.push(...registry.diagnostics.map((diagnostic) => diagnostic.message));
964
- issues.push(...survey.getBlockingExpressionIssues());
965
- if (issues.length) throw new Error(issues.join("\n"));
191
+ //#region src/expressions/shortcuts/selection.ts
192
+ /**
193
+ * Build a selected-option test joined with `not(ownerState.disabled)` for that option.
194
+ * `multiple` chooses membership instead of equality. The result is an ordinary expression tree;
195
+ * no shortcut node is persisted or handled specially by the evaluator.
196
+ */
197
+ function selectedAndEnabled(selection, optionId, multiple) {
198
+ return {
199
+ type: "function",
200
+ functionName: "and",
201
+ arguments: [{
202
+ type: "function",
203
+ functionName: multiple ? "list_contains" : "eq",
204
+ arguments: [selection, {
205
+ type: "const",
206
+ value: {
207
+ type: "reference",
208
+ value: optionId
209
+ }
210
+ }]
211
+ }, {
212
+ type: "function",
213
+ functionName: "not",
214
+ arguments: [{
215
+ type: "ownerState",
216
+ state: "disabled",
217
+ targetRef: { ownerId: optionId }
218
+ }]
219
+ }]
220
+ };
966
221
  }
967
222
  //#endregion
223
+ //#region src/survey/utils/group-utils.ts
224
+ /**
225
+ * Shared utilities for array operations.
226
+ */
227
+ /**
228
+ * Swap two elements in an array by index.
229
+ * @returns A new array with the elements swapped
230
+ */
231
+ function swapArrayElements(arr, fromIndex, toIndex) {
232
+ const result = [...arr];
233
+ [result[fromIndex], result[toIndex]] = [result[toIndex], result[fromIndex]];
234
+ return result;
235
+ }
236
+ /**
237
+ * Move an element from one index to another.
238
+ * @returns A new array with the element moved
239
+ */
240
+ function moveArrayElement(arr, fromIndex, toIndex) {
241
+ const result = [...arr];
242
+ const [moved] = result.splice(fromIndex, 1);
243
+ result.splice(toIndex, 0, moved);
244
+ return result;
245
+ }
246
+ //#endregion
247
+ //#region src/expressions/editors/expression-editor.ts
248
+ const constantNode = (value, editorConfig) => {
249
+ const node = expressionSchema.parse({
250
+ type: "const",
251
+ value,
252
+ editorConfig
253
+ });
254
+ if (node.type !== "const") throw new Error("Expected constant node");
255
+ return node;
256
+ };
257
+ /** Editor-state builders produce the canonical JSON tree, never runtime wrappers. */
258
+ const createFunctionExpression = (functionName, args, editorConfig) => ({
259
+ type: "function",
260
+ functionName,
261
+ arguments: expressionArguments(args),
262
+ editorConfig
263
+ });
264
+ const contextNode = (contextType, key, args, asType, editorConfig) => ({
265
+ type: "contextVariable",
266
+ contextType,
267
+ ...key ? { key } : {},
268
+ ...args ? { arguments: expressionArguments(args) } : {},
269
+ asType,
270
+ editorConfig
271
+ });
272
+ var ExpressionEditor = class {
273
+ returnType;
274
+ _editorConfig;
275
+ get editorConfig() {
276
+ return this._editorConfig;
277
+ }
278
+ withEditorConfig(editorConfig) {
279
+ this._editorConfig = editorConfig;
280
+ return this;
281
+ }
282
+ };
283
+ var ConstStringArrayEditor = class extends ExpressionEditor {
284
+ returnType = ValueType.stringArray;
285
+ _values;
286
+ constructor(values, editorConfig) {
287
+ super();
288
+ this._values = values;
289
+ this._editorConfig = editorConfig;
290
+ }
291
+ get values() {
292
+ return this._values;
293
+ }
294
+ set values(values) {
295
+ this._values = values;
296
+ }
297
+ getExpression() {
298
+ return constantNode({
299
+ type: ValueType.stringArray,
300
+ value: this._values
301
+ }, this._editorConfig);
302
+ }
303
+ };
304
+ var ConstStringEditor = class extends ExpressionEditor {
305
+ returnType = ValueType.string;
306
+ _value;
307
+ constructor(value, editorConfig) {
308
+ super();
309
+ this._value = value;
310
+ this._editorConfig = editorConfig;
311
+ }
312
+ get value() {
313
+ return this._value;
314
+ }
315
+ set value(value) {
316
+ this._value = value;
317
+ }
318
+ getExpression() {
319
+ return constantNode({
320
+ type: ValueType.string,
321
+ value: this._value
322
+ }, this._editorConfig);
323
+ }
324
+ };
325
+ var ConstNumberEditor = class extends ExpressionEditor {
326
+ returnType = ValueType.number;
327
+ _value;
328
+ constructor(value, editorConfig) {
329
+ super();
330
+ this._value = value;
331
+ this._editorConfig = editorConfig;
332
+ }
333
+ get value() {
334
+ return this._value;
335
+ }
336
+ set value(value) {
337
+ this._value = value;
338
+ }
339
+ getExpression() {
340
+ return constantNode({
341
+ type: ValueType.number,
342
+ value: this._value
343
+ }, this._editorConfig);
344
+ }
345
+ };
346
+ var ConstBooleanEditor = class extends ExpressionEditor {
347
+ returnType = ValueType.boolean;
348
+ _value;
349
+ constructor(value, editorConfig) {
350
+ super();
351
+ this._value = value;
352
+ this._editorConfig = editorConfig;
353
+ }
354
+ get value() {
355
+ return this._value;
356
+ }
357
+ set value(value) {
358
+ this._value = value;
359
+ }
360
+ getExpression() {
361
+ return constantNode({
362
+ type: ValueType.boolean,
363
+ value: this._value
364
+ }, this._editorConfig);
365
+ }
366
+ };
367
+ var ConstDateEditor = class extends ExpressionEditor {
368
+ returnType = ValueType.date;
369
+ _value;
370
+ constructor(value, editorConfig) {
371
+ super();
372
+ this._value = value;
373
+ this._editorConfig = editorConfig;
374
+ }
375
+ get value() {
376
+ return this._value;
377
+ }
378
+ set value(value) {
379
+ this._value = value;
380
+ }
381
+ getExpression() {
382
+ const value = this._value;
383
+ return constantNode({
384
+ type: ValueType.date,
385
+ value
386
+ }, this._editorConfig);
387
+ }
388
+ };
389
+ var ConstNumberArrayEditor = class extends ExpressionEditor {
390
+ returnType = ValueType.numberArray;
391
+ _values;
392
+ constructor(values, editorConfig) {
393
+ super();
394
+ this._values = values;
395
+ this._editorConfig = editorConfig;
396
+ }
397
+ get values() {
398
+ return this._values;
399
+ }
400
+ set values(values) {
401
+ this._values = values;
402
+ }
403
+ getExpression() {
404
+ return constantNode({
405
+ type: ValueType.numberArray,
406
+ value: this._values
407
+ }, this._editorConfig);
408
+ }
409
+ };
410
+ var ConstDateArrayEditor = class extends ExpressionEditor {
411
+ returnType = ValueType.dateArray;
412
+ _values;
413
+ constructor(values, editorConfig) {
414
+ super();
415
+ this._values = values;
416
+ this._editorConfig = editorConfig;
417
+ }
418
+ get values() {
419
+ return this._values;
420
+ }
421
+ set values(values) {
422
+ this._values = values;
423
+ }
424
+ getExpression() {
425
+ const value = this._values;
426
+ return constantNode({
427
+ type: ValueType.dateArray,
428
+ value
429
+ }, this._editorConfig);
430
+ }
431
+ };
432
+ var ResponseVariableEditor = class extends ExpressionEditor {
433
+ variableRef;
434
+ constructor(variableRef, variableType, editorConfig) {
435
+ super();
436
+ this.variableRef = variableRef;
437
+ this._editorConfig = editorConfig;
438
+ this.returnType = variableRef.method === "isDefined" ? ValueType.boolean : variableType;
439
+ }
440
+ getExpression() {
441
+ return {
442
+ type: "responseVariable",
443
+ variableRef: { ...this.variableRef },
444
+ editorConfig: this._editorConfig
445
+ };
446
+ }
447
+ };
448
+ var DisabledStateEditor = class extends ExpressionEditor {
449
+ ownerId;
450
+ returnType = ValueType.boolean;
451
+ constructor(ownerId, editorConfig) {
452
+ super();
453
+ this.ownerId = ownerId;
454
+ this._editorConfig = editorConfig;
455
+ }
456
+ getExpression() {
457
+ return {
458
+ type: "ownerState",
459
+ state: "disabled",
460
+ targetRef: { ownerId: this.ownerId },
461
+ editorConfig: this._editorConfig
462
+ };
463
+ }
464
+ };
465
+ var ContextVariableEditor = class extends ExpressionEditor {
466
+ _contextType;
467
+ _key;
468
+ _args;
469
+ _asType;
470
+ constructor(contextType, key, args, asType, editorConfig) {
471
+ super();
472
+ this._contextType = contextType;
473
+ this._key = key;
474
+ this._args = args;
475
+ this._asType = asType;
476
+ this._editorConfig = editorConfig;
477
+ }
478
+ getExpression() {
479
+ return contextNode(this._contextType, this._key?.getExpression(), this._args?.map((arg) => arg.getExpression()), this._asType, this._editorConfig);
480
+ }
481
+ };
482
+ var CtxLocaleEditor = class extends ContextVariableEditor {
483
+ returnType = ValueType.string;
484
+ constructor(editorConfig) {
485
+ super("locale", void 0, void 0, ValueType.string, editorConfig);
486
+ }
487
+ };
488
+ var CtxCurrentDateEditor = class extends ContextVariableEditor {
489
+ returnType = ValueType.date;
490
+ constructor(editorConfig) {
491
+ super("currentDate", void 0, void 0, ValueType.date, editorConfig);
492
+ }
493
+ };
494
+ var CtxPFlagIsDefinedEditor = class extends ContextVariableEditor {
495
+ returnType = ValueType.boolean;
496
+ constructor(key, editorConfig) {
497
+ super("participantFlag", key, void 0, ValueType.boolean, editorConfig);
498
+ }
499
+ };
500
+ var CtxPFlagStringEditor = class extends ContextVariableEditor {
501
+ returnType = ValueType.string;
502
+ constructor(key, editorConfig) {
503
+ super("participantFlag", key, void 0, ValueType.string, editorConfig);
504
+ }
505
+ };
506
+ var CtxPFlagNumEditor = class extends ContextVariableEditor {
507
+ returnType = ValueType.number;
508
+ constructor(key, editorConfig) {
509
+ super("participantFlag", key, void 0, ValueType.number, editorConfig);
510
+ }
511
+ };
512
+ var CtxPFlagDateEditor = class extends ContextVariableEditor {
513
+ returnType = ValueType.date;
514
+ constructor(key, editorConfig) {
515
+ super("participantFlag", key, void 0, ValueType.date, editorConfig);
516
+ }
517
+ };
518
+ var CtxCustomValueEditor = class extends ContextVariableEditor {
519
+ constructor(key, asType, editorConfig) {
520
+ super("customValue", key, void 0, asType, editorConfig);
521
+ this.returnType = asType;
522
+ }
523
+ };
524
+ var GroupExpressionEditor = class extends ExpressionEditor {
525
+ _args;
526
+ constructor(args, editorConfig) {
527
+ super();
528
+ this._args = args;
529
+ this._editorConfig = editorConfig;
530
+ }
531
+ get args() {
532
+ return this._args;
533
+ }
534
+ addArg(arg, position) {
535
+ if (position === void 0) this._args.push(arg);
536
+ else this._args.splice(position, 0, arg);
537
+ }
538
+ removeArg(position) {
539
+ this._args.splice(position, 1);
540
+ }
541
+ replaceArg(position, arg) {
542
+ this._args[position] = arg;
543
+ }
544
+ swapArgs(activeIndex, overIndex) {
545
+ this._args = moveArrayElement(this._args, activeIndex, overIndex);
546
+ }
547
+ };
548
+ var AndExpressionEditor = class extends GroupExpressionEditor {
549
+ returnType = ValueType.boolean;
550
+ constructor(args, editorConfig) {
551
+ if (args.some((arg) => arg.returnType !== ValueType.boolean)) throw new Error("And expression editor must have boolean arguments");
552
+ super(args, editorConfig);
553
+ }
554
+ getExpression() {
555
+ return createFunctionExpression("and", this.args.map((arg) => arg.getExpression()), this._editorConfig);
556
+ }
557
+ };
558
+ var OrExpressionEditor = class extends GroupExpressionEditor {
559
+ returnType = ValueType.boolean;
560
+ constructor(args, editorConfig) {
561
+ if (args.some((arg) => arg.returnType !== ValueType.boolean)) throw new Error("Or expression editor must have boolean arguments");
562
+ super(args, editorConfig);
563
+ }
564
+ getExpression() {
565
+ return createFunctionExpression("or", this.args.map((arg) => arg.getExpression()), this._editorConfig);
566
+ }
567
+ };
568
+ var StrListContainsExpressionEditor = class extends ExpressionEditor {
569
+ returnType = ValueType.boolean;
570
+ _list;
571
+ _item;
572
+ constructor(list, item, editorConfig) {
573
+ super();
574
+ if (list.returnType !== ValueType.stringArray) throw new Error("List contains expression editor must have a string array list");
575
+ if (item.returnType !== ValueType.string) throw new Error("List contains expression editor must have a string item");
576
+ this._list = list;
577
+ this._item = item;
578
+ this._editorConfig = editorConfig;
579
+ }
580
+ get list() {
581
+ return this._list;
582
+ }
583
+ get item() {
584
+ return this._item;
585
+ }
586
+ set list(list) {
587
+ this._list = list;
588
+ }
589
+ set item(item) {
590
+ this._item = item;
591
+ }
592
+ getExpression() {
593
+ return createFunctionExpression("list_contains", [this._list?.getExpression(), this._item?.getExpression()], this._editorConfig);
594
+ }
595
+ };
596
+ var StrEqExpressionEditor = class extends ExpressionEditor {
597
+ returnType = ValueType.boolean;
598
+ _a;
599
+ _b;
600
+ constructor(a, b, editorConfig) {
601
+ super();
602
+ this._a = a;
603
+ this._b = b;
604
+ this._editorConfig = editorConfig;
605
+ }
606
+ get a() {
607
+ return this._a;
608
+ }
609
+ get b() {
610
+ return this._b;
611
+ }
612
+ set a(a) {
613
+ this._a = a;
614
+ }
615
+ set b(b) {
616
+ this._b = b;
617
+ }
618
+ getExpression() {
619
+ return createFunctionExpression("str_eq", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
620
+ }
621
+ };
622
+ var EqExpressionEditor = class extends ExpressionEditor {
623
+ returnType = ValueType.boolean;
624
+ _a;
625
+ _b;
626
+ constructor(a, b, editorConfig) {
627
+ super();
628
+ this._a = a;
629
+ this._b = b;
630
+ this._editorConfig = editorConfig;
631
+ }
632
+ get a() {
633
+ return this._a;
634
+ }
635
+ get b() {
636
+ return this._b;
637
+ }
638
+ set a(a) {
639
+ this._a = a;
640
+ }
641
+ set b(b) {
642
+ this._b = b;
643
+ }
644
+ getExpression() {
645
+ return createFunctionExpression("eq", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
646
+ }
647
+ };
648
+ var GtExpressionEditor = class extends ExpressionEditor {
649
+ returnType = ValueType.boolean;
650
+ _a;
651
+ _b;
652
+ constructor(a, b, editorConfig) {
653
+ super();
654
+ this._a = a;
655
+ this._b = b;
656
+ this._editorConfig = editorConfig;
657
+ }
658
+ get a() {
659
+ return this._a;
660
+ }
661
+ get b() {
662
+ return this._b;
663
+ }
664
+ set a(a) {
665
+ this._a = a;
666
+ }
667
+ set b(b) {
668
+ this._b = b;
669
+ }
670
+ getExpression() {
671
+ return createFunctionExpression("gt", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
672
+ }
673
+ };
674
+ var GteExpressionEditor = class extends ExpressionEditor {
675
+ returnType = ValueType.boolean;
676
+ _a;
677
+ _b;
678
+ constructor(a, b, editorConfig) {
679
+ super();
680
+ this._a = a;
681
+ this._b = b;
682
+ this._editorConfig = editorConfig;
683
+ }
684
+ get a() {
685
+ return this._a;
686
+ }
687
+ get b() {
688
+ return this._b;
689
+ }
690
+ set a(a) {
691
+ this._a = a;
692
+ }
693
+ set b(b) {
694
+ this._b = b;
695
+ }
696
+ getExpression() {
697
+ return createFunctionExpression("gte", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
698
+ }
699
+ };
700
+ var LtExpressionEditor = class extends ExpressionEditor {
701
+ returnType = ValueType.boolean;
702
+ _a;
703
+ _b;
704
+ constructor(a, b, editorConfig) {
705
+ super();
706
+ this._a = a;
707
+ this._b = b;
708
+ this._editorConfig = editorConfig;
709
+ }
710
+ get a() {
711
+ return this._a;
712
+ }
713
+ get b() {
714
+ return this._b;
715
+ }
716
+ set a(a) {
717
+ this._a = a;
718
+ }
719
+ set b(b) {
720
+ this._b = b;
721
+ }
722
+ getExpression() {
723
+ return createFunctionExpression("lt", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
724
+ }
725
+ };
726
+ var LteExpressionEditor = class extends ExpressionEditor {
727
+ returnType = ValueType.boolean;
728
+ _a;
729
+ _b;
730
+ constructor(a, b, editorConfig) {
731
+ super();
732
+ this._a = a;
733
+ this._b = b;
734
+ this._editorConfig = editorConfig;
735
+ }
736
+ get a() {
737
+ return this._a;
738
+ }
739
+ get b() {
740
+ return this._b;
741
+ }
742
+ set b(b) {
743
+ this._b = b;
744
+ }
745
+ set a(a) {
746
+ this._a = a;
747
+ }
748
+ getExpression() {
749
+ return createFunctionExpression("lte", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
750
+ }
751
+ };
752
+ var InRangeExpressionEditor = class extends ExpressionEditor {
753
+ returnType = ValueType.boolean;
754
+ value;
755
+ min;
756
+ max;
757
+ inclusive;
758
+ constructor(value, min, max, inclusive, editorConfig) {
759
+ super();
760
+ this.value = value;
761
+ this.min = min;
762
+ this.max = max;
763
+ this.inclusive = inclusive;
764
+ this._editorConfig = editorConfig;
765
+ }
766
+ getExpression() {
767
+ return createFunctionExpression("in_range", [
768
+ this.value?.getExpression(),
769
+ this.min?.getExpression(),
770
+ this.max?.getExpression(),
771
+ this.inclusive?.getExpression()
772
+ ], this._editorConfig);
773
+ }
774
+ };
775
+ var SumExpressionEditor = class extends GroupExpressionEditor {
776
+ returnType = ValueType.number;
777
+ constructor(args, editorConfig) {
778
+ super(args, editorConfig);
779
+ }
780
+ getExpression() {
781
+ return createFunctionExpression("sum", this.args.map((arg) => arg.getExpression()), this._editorConfig);
782
+ }
783
+ };
784
+ var MinExpressionEditor = class extends GroupExpressionEditor {
785
+ returnType = ValueType.number;
786
+ constructor(args, editorConfig) {
787
+ super(args, editorConfig);
788
+ }
789
+ getExpression() {
790
+ return createFunctionExpression("min", this.args.map((arg) => arg.getExpression()), this._editorConfig);
791
+ }
792
+ };
793
+ var MaxExpressionEditor = class extends GroupExpressionEditor {
794
+ returnType = ValueType.number;
795
+ constructor(args, editorConfig) {
796
+ super(args, editorConfig);
797
+ }
798
+ getExpression() {
799
+ return createFunctionExpression("max", this.args.map((arg) => arg.getExpression()), this._editorConfig);
800
+ }
801
+ };
802
+ //#endregion
803
+ //#region src/expressions/editors/expression-editor-generators.ts
804
+ const const_string_array = (...values) => {
805
+ return new ConstStringArrayEditor(values);
806
+ };
807
+ const const_string = (value) => {
808
+ return new ConstStringEditor(value);
809
+ };
810
+ const const_number_array = (...values) => {
811
+ return new ConstNumberArrayEditor(values);
812
+ };
813
+ const const_number = (value) => {
814
+ return new ConstNumberEditor(value);
815
+ };
816
+ const const_boolean = (value) => {
817
+ return new ConstBooleanEditor(value);
818
+ };
819
+ const const_date = (value) => {
820
+ return new ConstDateEditor(value);
821
+ };
822
+ const const_date_array = (...values) => {
823
+ return new ConstDateArrayEditor(values);
824
+ };
825
+ const response_string = (valueRef) => {
826
+ return new ResponseVariableEditor(valueRef, ValueType.string);
827
+ };
828
+ const response_string_array = (valueRef) => {
829
+ return new ResponseVariableEditor(valueRef, ValueType.stringArray);
830
+ };
831
+ const response_number = (valueRef) => {
832
+ return new ResponseVariableEditor(valueRef, ValueType.number);
833
+ };
834
+ const response_boolean = (valueRef) => {
835
+ return new ResponseVariableEditor(valueRef, ValueType.boolean);
836
+ };
837
+ const response_date = (valueRef) => {
838
+ return new ResponseVariableEditor(valueRef, ValueType.date);
839
+ };
840
+ const response_number_array = (valueRef) => {
841
+ return new ResponseVariableEditor(valueRef, ValueType.numberArray);
842
+ };
843
+ const response_date_array = (valueRef) => {
844
+ return new ResponseVariableEditor(valueRef, ValueType.dateArray);
845
+ };
846
+ const ctx_locale = () => {
847
+ return new CtxLocaleEditor();
848
+ };
849
+ const ctx_current_date = () => {
850
+ return new CtxCurrentDateEditor();
851
+ };
852
+ const ctx_pflag_is_defined = (key) => {
853
+ return new CtxPFlagIsDefinedEditor(key);
854
+ };
855
+ const ctx_pflag_string = (key) => {
856
+ return new CtxPFlagStringEditor(key);
857
+ };
858
+ const ctx_pflag_num = (key) => {
859
+ return new CtxPFlagNumEditor(key);
860
+ };
861
+ const ctx_pflag_date = (key) => {
862
+ return new CtxPFlagDateEditor(key);
863
+ };
864
+ const ctx_custom_value = (key, dType) => {
865
+ return new CtxCustomValueEditor(key, dType);
866
+ };
867
+ const and = (...args) => {
868
+ return new AndExpressionEditor(args);
869
+ };
870
+ const or = (...args) => {
871
+ return new OrExpressionEditor(args);
872
+ };
873
+ const str_list_contains = (list, item) => {
874
+ return new StrListContainsExpressionEditor(list, item);
875
+ };
876
+ /** Number of list entries, or of entries that also appear in `filter` (e.g. listed options). */
877
+ const list_count = (list, filter) => new FunctionCallEditor("list_count", filter ? [list, filter] : [list], ValueType.number);
878
+ /** `then` when the condition is true, otherwise `otherwise` (missing when omitted). */
879
+ const if_else = (condition, then, otherwise) => new FunctionCallEditor("if", otherwise ? [
880
+ condition,
881
+ then,
882
+ otherwise
883
+ ] : [condition, then], then.returnType ?? otherwise?.returnType);
884
+ const str_eq = (a, b) => {
885
+ return new StrEqExpressionEditor(a, b);
886
+ };
887
+ const eq = (a, b) => {
888
+ return new EqExpressionEditor(a, b);
889
+ };
890
+ const gt = (a, b) => {
891
+ return new GtExpressionEditor(a, b);
892
+ };
893
+ const gte = (a, b) => {
894
+ return new GteExpressionEditor(a, b);
895
+ };
896
+ const lt = (a, b) => {
897
+ return new LtExpressionEditor(a, b);
898
+ };
899
+ const lte = (a, b) => {
900
+ return new LteExpressionEditor(a, b);
901
+ };
902
+ const in_range = (value, min, max, inclusive) => {
903
+ return new InRangeExpressionEditor(value, min, max, inclusive);
904
+ };
905
+ const sum = (...args) => {
906
+ return new SumExpressionEditor(args);
907
+ };
908
+ const min = (...args) => {
909
+ return new MinExpressionEditor(args);
910
+ };
911
+ const max = (...args) => {
912
+ return new MaxExpressionEditor(args);
913
+ };
914
+ var FunctionCallEditor = class extends ExpressionEditor {
915
+ name;
916
+ args;
917
+ returnType;
918
+ constructor(name, args, returnType) {
919
+ super();
920
+ this.name = name;
921
+ this.args = args;
922
+ this.returnType = returnType;
923
+ }
924
+ getExpression() {
925
+ return createFunctionExpression(this.name, this.args.map((arg) => arg.getExpression()), this.editorConfig);
926
+ }
927
+ };
928
+ const date_eq = (a, b) => new FunctionCallEditor("date_eq", [a, b], ValueType.boolean);
929
+ const date_gt = (a, b) => new FunctionCallEditor("date_gt", [a, b], ValueType.boolean);
930
+ const date_gte = (a, b) => new FunctionCallEditor("date_gte", [a, b], ValueType.boolean);
931
+ const date_lt = (a, b) => new FunctionCallEditor("date_lt", [a, b], ValueType.boolean);
932
+ const date_lte = (a, b) => new FunctionCallEditor("date_lte", [a, b], ValueType.boolean);
933
+ const date_add_days = (a, b) => new FunctionCallEditor("date_add_days", [a, b], ValueType.date);
934
+ const date_add_months = (a, b) => new FunctionCallEditor("date_add_months", [a, b], ValueType.date);
935
+ const date_add_years = (a, b) => new FunctionCallEditor("date_add_years", [a, b], ValueType.date);
936
+ /** Signed completed units in a - b, e.g. age as `date_diff(ctx_current_date(), birth, "years")`. */
937
+ const date_diff = (a, b, unit) => new FunctionCallEditor("date_diff", [
938
+ a,
939
+ b,
940
+ const_string(unit)
941
+ ], ValueType.number);
942
+ const date_min = (...args) => new FunctionCallEditor("date_min", args, ValueType.date);
943
+ const date_max = (...args) => new FunctionCallEditor("date_max", args, ValueType.date);
944
+ const is_disabled = (ownerId) => new DisabledStateEditor(ownerId);
945
+ //#endregion
946
+ //#region src/survey/composition/registry.ts
947
+ function defineElement(definition) {
948
+ const schema = contentOwner(z.strictObject({
949
+ ...elementShape,
950
+ elementType: z.literal(definition.elementType),
951
+ config: definition.config.nonoptional()
952
+ }), definition.roles ?? []);
953
+ return {
954
+ elementType: definition.elementType,
955
+ labelByLocale: definition.labelByLocale,
956
+ schema,
957
+ inspect(input) {
958
+ const node = schema.parse(input);
959
+ return {
960
+ slots: [],
961
+ bindings: [],
962
+ followUps: [],
963
+ fullWidth: false,
964
+ minWidthRem: 12,
965
+ componentParents: {},
966
+ ...definition.facts?.(node.config, node.id)
967
+ };
968
+ },
969
+ compute(input, slotId, read) {
970
+ return definition.compute?.(schema.parse(input).config, slotId, read);
971
+ },
972
+ validate(input, read, evaluate) {
973
+ const node = schema.parse(input);
974
+ return definition.validate?.(node.config, node.id, read, evaluate) ?? [];
975
+ }
976
+ };
977
+ }
978
+ /** A registry is finalized once. Its one recursive schema is shared by built-in and custom bodies. */
979
+ function createElementRegistry(create) {
980
+ const definitions = create(z.lazy(() => composition));
981
+ const byType = /* @__PURE__ */ new Map();
982
+ for (const definition of definitions) {
983
+ if (byType.has(definition.elementType)) throw new Error(`Duplicate element registration: ${definition.elementType}`);
984
+ byType.set(definition.elementType, definition);
985
+ }
986
+ const variants = definitions.map((definition) => definition.schema);
987
+ const element = variants.length ? z.union(variants) : z.never();
988
+ const composition = createCompositionSchema(element);
989
+ return {
990
+ definitions,
991
+ labels: new Map(definitions.map((definition) => [definition.elementType, definition.labelByLocale ?? {}])),
992
+ elementSchema: element,
993
+ bodySchema: composition,
994
+ surveySchema: createSurveySchema(composition),
995
+ get(elementType) {
996
+ const definition = byType.get(elementType);
997
+ if (!definition) throw new Error(`Unknown element type: ${elementType}`);
998
+ return definition;
999
+ }
1000
+ };
1001
+ }
1002
+ //#endregion
1003
+ //#region src/survey/composition/semantics.ts
1004
+ /** Structural context is independent of an element's explicit accessible label. */
1005
+ function getElementSemanticContext(survey, elementId) {
1006
+ const owner = survey.owners.get(elementId);
1007
+ if (!owner) throw new Error(`Unknown element '${elementId}'`);
1008
+ let ancestor = owner.parentId ? survey.owners.get(owner.parentId) : void 0;
1009
+ while (ancestor && ancestor.followUpTriggerId === owner.followUpTriggerId) {
1010
+ if (ancestor.kind === "section" || ancestor.kind === "item") return ancestor;
1011
+ ancestor = ancestor.parentId ? survey.owners.get(ancestor.parentId) : void 0;
1012
+ }
1013
+ }
1014
+ function resolveElementName(survey, elementId, locales) {
1015
+ const owner = survey.owners.get(elementId);
1016
+ if (!owner) throw new Error(`Unknown element '${elementId}'`);
1017
+ const name = (ownerId, role) => {
1018
+ if (!survey.owners.get(ownerId)?.roles.some((entry) => entry.role === role)) return void 0;
1019
+ const resolved = resolveParticipantContent(survey, ownerId, role, locales);
1020
+ const text = getContentPlainText(resolved?.content).trim();
1021
+ return text && resolved ? {
1022
+ ownerId,
1023
+ role,
1024
+ text,
1025
+ content: resolved.content,
1026
+ locale: resolved.locale
1027
+ } : void 0;
1028
+ };
1029
+ const own = name(elementId, "label");
1030
+ if (own) return own;
1031
+ let ancestor = owner.parentId ? survey.owners.get(owner.parentId) : void 0;
1032
+ while (ancestor) {
1033
+ const current = ancestor;
1034
+ if ([...survey.elements.values()].filter((element) => {
1035
+ const candidate = survey.owners.get(element.node.id);
1036
+ return element.facts.bindings.length > 0 && candidate.followUpTriggerId === owner.followUpTriggerId && current.path.every((part, index) => candidate.path[index] === part);
1037
+ }).length === 1) {
1038
+ const inherited = name(ancestor.id, "title") ?? name(ancestor.id, "label");
1039
+ if (inherited) return inherited;
1040
+ }
1041
+ if (ancestor.kind === "section" || ancestor.id === owner.followUpTriggerId) return void 0;
1042
+ ancestor = ancestor.parentId ? survey.owners.get(ancestor.parentId) : void 0;
1043
+ }
1044
+ }
1045
+ //#endregion
1046
+ //#region src/engine/validation.ts
1047
+ function validateSurveyAnswers(plan, answers, context, options) {
1048
+ const snapshot = plan.evaluate({
1049
+ answers,
1050
+ context
1051
+ });
1052
+ const evaluationDiagnostics = [];
1053
+ const eligible = (slotId) => {
1054
+ const binding = plan.bindings.get(slotId);
1055
+ return !!binding && hasPermittedChange(binding, snapshot);
1056
+ };
1057
+ const evaluate = (expression, path) => {
1058
+ try {
1059
+ return evaluateExpression(expression, {
1060
+ ...context,
1061
+ answer: snapshot.answer,
1062
+ disabled: snapshot.disabled
1063
+ });
1064
+ } catch (error) {
1065
+ if (!(error instanceof ExpressionEvaluationError)) throw error;
1066
+ evaluationDiagnostics.push({
1067
+ code: "validation-evaluation",
1068
+ severity: "warning",
1069
+ path,
1070
+ message: error.message
1071
+ });
1072
+ return;
1073
+ }
1074
+ };
1075
+ const issues = [];
1076
+ const authorWarnings = [];
1077
+ const active = (slotId) => {
1078
+ const binding = plan.bindings.get(slotId);
1079
+ return !!binding && [binding.controlId, ...binding.visibilityIds ?? []].every(snapshot.visible);
1080
+ };
1081
+ const included = (itemId) => !options.itemIds || options.itemIds.includes(itemId);
1082
+ for (const element of plan.document.elements.values()) {
1083
+ if (!included(element.itemId)) continue;
1084
+ for (const binding of element.facts.bindings) {
1085
+ if (!binding.selection || !active(binding.slotId) || [binding.controlId, ...binding.visibilityIds ?? []].some(snapshot.disabled)) continue;
1086
+ if (!hasPermittedChange(binding, snapshot)) authorWarnings.push({
1087
+ ownerId: binding.controlId,
1088
+ code: "non-interactive-choice",
1089
+ message: "Option locks, visibility, or the selection limit leave this choice with no permitted participant change. Its constraints will not block progress; its active answer is still submitted."
1090
+ });
1091
+ if (binding.selection.multiple) {
1092
+ const answer = snapshot.answer(binding.slotId);
1093
+ if (answer?.type === "reference[]") for (const optionId of answer.value.filter(snapshot.disabled)) authorWarnings.push({
1094
+ ownerId: optionId,
1095
+ code: "locked-checkbox-selection",
1096
+ message: "This selected checkbox is now locked and cannot be unticked. It remains an active, submitted answer. Use visibility if the answer should stop applying."
1097
+ });
1098
+ }
1099
+ }
1100
+ for (const issue of plan.document.registry.get(element.node.elementType).validate(element.node, snapshot.answer, (expression) => evaluate(expression, element.path))) if (eligible(issue.slotId)) issues.push(issue);
1101
+ else if (active(issue.slotId)) authorWarnings.push({
1102
+ ownerId: issue.messageOwnerId ?? plan.bindings.get(issue.slotId).controlId,
1103
+ code: "locked-input-constraint",
1104
+ message: `The ${issue.code} constraint is not satisfied, but this input currently permits no participant change. It will not block progress; its active answer is still submitted.`
1105
+ });
1106
+ }
1107
+ for (const item of plan.document.data.surveyItems) {
1108
+ if (item.itemType !== "content" || !included(item.id)) continue;
1109
+ for (const rule of item.config.validationRules ?? []) {
1110
+ if (rule.phase === "submit" && options.phase !== "submit") continue;
1111
+ const targets = rule.targets.filter(eligible);
1112
+ if (!rule.targets.filter(active).length) continue;
1113
+ if (rule.when) {
1114
+ const result = evaluate(rule.when, [...plan.document.owners.get(rule.id).path, "when"]);
1115
+ if (result?.type !== "boolean" || !result.value) continue;
1116
+ }
1117
+ const result = evaluate(rule.expression, [...plan.document.owners.get(rule.id).path, "expression"]);
1118
+ if (result?.type !== "boolean" || !result.value) {
1119
+ issues.push(...targets.map((slotId) => ({
1120
+ slotId,
1121
+ code: "rule",
1122
+ messageOwnerId: rule.id
1123
+ })));
1124
+ if (!targets.length) authorWarnings.push({
1125
+ ownerId: rule.id,
1126
+ code: "locked-rule-targets",
1127
+ message: "This applicable rule fails, but none of its active targets permits a participant change. It will not block progress. Check the rule and disabling conditions."
1128
+ });
1129
+ }
1130
+ }
1131
+ }
1132
+ return {
1133
+ issues,
1134
+ diagnostics: [...snapshot.diagnostics, ...evaluationDiagnostics],
1135
+ authorWarnings
1136
+ };
1137
+ }
1138
+ const authoringDependencyPlans = /* @__PURE__ */ new WeakMap();
1139
+ const staticWarnings = /* @__PURE__ */ new WeakMap();
1140
+ /** Conservative proofs only: dynamic conditions and prefills are checked in a real preview. */
1141
+ function staticConstraintWarnings(survey, plan) {
1142
+ const literal = (expression, value) => expression?.type === "const" && expression.value?.type === "boolean" && expression.value.value === value;
1143
+ const parents = (id) => {
1144
+ const owner = survey.owners.get(id);
1145
+ return [...owner.parentId ? [owner.parentId] : [], ...owner.elementId ? ownValue(survey.elements.get(owner.elementId)?.facts.componentParents, id) ?? [] : []];
1146
+ };
1147
+ const visible = (id) => {
1148
+ const owner = survey.owners.get(id);
1149
+ return !owner.followUpTriggerId && (!owner.visibility || literal(owner.visibility, true)) && parents(id).every(visible);
1150
+ };
1151
+ const hidden = (id) => literal(survey.owners.get(id).visibility, false) || parents(id).some(hidden);
1152
+ const disabled = (id) => literal(survey.owners.get(id).disabled, true) || parents(id).some(disabled);
1153
+ const locked = (slotId) => {
1154
+ const binding = plan.bindings.get(slotId);
1155
+ if (!binding) return false;
1156
+ const controls = [binding.controlId, ...binding.visibilityIds ?? []];
1157
+ return controls.every(visible) && (controls.some(disabled) || !!binding.selection?.options.every((id) => hidden(id) || disabled(id)));
1158
+ };
1159
+ const warnings = [];
1160
+ for (const element of survey.elements.values()) {
1161
+ if (!element.facts.bindings.some((binding) => locked(binding.slotId))) continue;
1162
+ for (const issue of survey.registry.get(element.node.elementType).validate(element.node, () => void 0, () => void 0)) if ((issue.code === "required" || issue.code === "mustBeTrue") && locked(issue.slotId) && !ownValue(element.node.prefills, issue.slotId)?.length) warnings.push({
1163
+ ownerId: issue.messageOwnerId ?? plan.bindings.get(issue.slotId).controlId,
1164
+ code: "locked-input-constraint",
1165
+ message: `The ${issue.code} constraint starts unsatisfied on an input with no permitted participant change and no prefill. It will not block progress. Check the requirement and disabling conditions.`
1166
+ });
1167
+ }
1168
+ for (const item of survey.data.surveyItems) {
1169
+ if (item.itemType !== "content") continue;
1170
+ for (const rule of item.config.validationRules ?? []) {
1171
+ if (!literal(rule.expression, false) || rule.when && !literal(rule.when, true)) continue;
1172
+ if (rule.targets.length && rule.targets.every(locked)) warnings.push({
1173
+ ownerId: rule.id,
1174
+ code: "locked-rule-targets",
1175
+ message: "This rule always fails, but all its targets are locked. It will not block progress. Check the rule and disabling conditions."
1176
+ });
1177
+ }
1178
+ }
1179
+ return warnings;
1180
+ }
1181
+ function getAuthoringWarnings(survey, locale) {
1182
+ const warnings = [];
1183
+ const itemsWithElements = new Set([...survey.elements.values()].map((element) => element.itemId));
1184
+ for (const item of survey.data.surveyItems) if (item.itemType === "content" && !itemsWithElements.has(item.id)) warnings.push({
1185
+ ownerId: item.id,
1186
+ code: "empty-content-item",
1187
+ message: "Add an element to this empty item, or remove the item."
1188
+ });
1189
+ for (const owner of survey.owners.values()) for (const role of owner.roles.filter((role) => role.recommended)) if (!hasLocalizationText(readLocalizationContent(survey, {
1190
+ scope: "content",
1191
+ ownerId: owner.id,
1192
+ role: role.role
1193
+ }, locale))) warnings.push({
1194
+ ownerId: owner.id,
1195
+ code: "missing-content",
1196
+ message: `Add ${role.role} to make this structure understandable`
1197
+ });
1198
+ let plan = authoringDependencyPlans.get(survey.elements);
1199
+ if (!plan) {
1200
+ plan = new SurveyStateGraph(survey, { admission: "draft" });
1201
+ authoringDependencyPlans.set(survey.elements, plan);
1202
+ }
1203
+ let conflicts = staticWarnings.get(survey.elements);
1204
+ if (!conflicts) {
1205
+ conflicts = staticConstraintWarnings(survey, plan);
1206
+ staticWarnings.set(survey.elements, conflicts);
1207
+ }
1208
+ warnings.push(...conflicts);
1209
+ for (const item of survey.data.surveyItems) {
1210
+ if (item.itemType !== "content") continue;
1211
+ for (const rule of item.config.validationRules ?? []) {
1212
+ if (rule.phase === "submit") continue;
1213
+ if ([rule.expression, ...rule.when ? [rule.when] : []].flatMap((expression) => [...plan.storedDependencies(expression)]).some((id) => {
1214
+ const dependencyItem = survey.slots.get(id)?.itemId;
1215
+ return dependencyItem && dependencyItem !== item.id && !isStaticallyBefore(survey, dependencyItem, item.id);
1216
+ })) warnings.push({
1217
+ ownerId: rule.id,
1218
+ code: "cross-item-page-rule",
1219
+ message: "This rule runs before leaving the page and reads answers from another item. If those answers are on a later page, the participant may be unable to continue. Consider submit-only validation or an explicit condition for unanswered dependencies."
1220
+ });
1221
+ }
1222
+ }
1223
+ for (const element of survey.elements.values()) for (const binding of element.facts.bindings) if (binding.labelRefs) {
1224
+ for (const ref of binding.labelRefs) if (!hasLocalizationText(readLocalizationContent(survey, {
1225
+ scope: "content",
1226
+ ...ref
1227
+ }, locale)) && !warnings.some((warning) => warning.ownerId === ref.ownerId && warning.code === "missing-content")) warnings.push({
1228
+ ownerId: ref.ownerId,
1229
+ code: "missing-accessible-name",
1230
+ message: `Add ${ref.role} to name this input in the selected language`
1231
+ });
1232
+ } else if (!resolveElementName(survey, element.node.id, [locale]) && !warnings.some((warning) => warning.ownerId === element.node.id && warning.code === "missing-accessible-name")) warnings.push({
1233
+ ownerId: element.node.id,
1234
+ code: "missing-accessible-name",
1235
+ message: "Add a label, or an unambiguous enclosing title, in the selected language"
1236
+ });
1237
+ for (const element of survey.elements.values()) for (const binding of element.facts.bindings) {
1238
+ if (!binding.selection?.multiple) continue;
1239
+ for (const id of binding.selection.options) {
1240
+ const condition = survey.owners.get(id)?.disabled;
1241
+ if (condition && [...plan.storedDependencies(condition)].some((slotId) => slotId !== binding.slotId)) warnings.push({
1242
+ ownerId: id,
1243
+ code: "external-checkbox-lock",
1244
+ message: "This condition depends on another answer. An already selected checkbox can become locked and will still be submitted. Use visibility if the selection should stop applying."
1245
+ });
1246
+ }
1247
+ }
1248
+ return warnings;
1249
+ }
1250
+ /** A shared shuffled ancestor only changes order between its distinct children. */
1251
+ function isStaticallyBefore(survey, earlierId, laterId) {
1252
+ const earlier = [...survey.getItemPath(earlierId), earlierId];
1253
+ const later = [...survey.getItemPath(laterId), laterId];
1254
+ const branch = earlier.findIndex((id, index) => id !== later[index]);
1255
+ if (branch < 1) return false;
1256
+ const parent = survey.getItem(earlier[branch - 1]);
1257
+ return parent?.itemType === "group" && !parent.config.shuffleItems && parent.config.items.indexOf(earlier[branch]) < parent.config.items.indexOf(later[branch]);
1258
+ }
1259
+ //#endregion
1260
+ //#region src/survey/composition/diagnostics.ts
1261
+ /** Observed author-only conflicts use the same validation result as the player and host. */
1262
+ function getSessionDiagnostics(session) {
1263
+ const result = session.validate("submit");
1264
+ return [...result.authorWarnings.map((warning) => {
1265
+ const owner = session.document.owners.get(warning.ownerId);
1266
+ return {
1267
+ ...warning,
1268
+ severity: "warning",
1269
+ path: owner?.path ?? [],
1270
+ ownerItemId: owner?.itemId
1271
+ };
1272
+ }), ...result.diagnostics.map((diagnostic) => {
1273
+ const owner = ownerAtPath(session.document, diagnostic.path);
1274
+ return {
1275
+ ...diagnostic,
1276
+ ownerId: owner?.id,
1277
+ ownerItemId: owner?.itemId
1278
+ };
1279
+ })];
1280
+ }
1281
+ function ownerAtPath(survey, path) {
1282
+ return [...survey.owners.values()].filter((owner) => owner.path.length <= path.length && owner.path.every((part, index) => part === path[index])).sort((a, b) => b.path.length - a.path.length)[0];
1283
+ }
1284
+ /**
1285
+ * Every dangling reference an editor draft admits: missing answers, state targets, options,
1286
+ * owners and non-flow items from the document index, plus comparisons with removed option
1287
+ * identities reported by the compiled graph. Always empty for a runnable survey.
1288
+ */
1289
+ function getBrokenReferences(survey) {
1290
+ return getDefinitionDiagnostics(survey).filter((diagnostic) => diagnostic.severity === "error" && diagnostic.kind === "reference");
1291
+ }
1292
+ const compiledDiagnostics = /* @__PURE__ */ new WeakMap();
1293
+ /** Use the authoritative definition/compiler diagnostics, including state reads and custom definitions. */
1294
+ function getDefinitionDiagnostics(survey, locale) {
1295
+ let compiled = compiledDiagnostics.get(survey.elements);
1296
+ if (!compiled) {
1297
+ try {
1298
+ compiled = new SurveyStateGraph(survey, { admission: "draft" }).admittedDiagnostics;
1299
+ } catch (error) {
1300
+ if (error instanceof DefinitionError) compiled = error.diagnostics;
1301
+ else throw error;
1302
+ }
1303
+ compiledDiagnostics.set(survey.elements, compiled);
1304
+ }
1305
+ const located = [...survey.diagnostics, ...compiled].map((diagnostic) => {
1306
+ const owner = ownerAtPath(survey, diagnostic.path) ?? (diagnostic.path[0] === "translations" && diagnostic.path[2] === "content" ? survey.owners.get(String(diagnostic.path[3])) : void 0);
1307
+ return {
1308
+ ...diagnostic,
1309
+ ownerItemId: owner?.itemId,
1310
+ ownerId: owner?.id
1311
+ };
1312
+ });
1313
+ if (locale) try {
1314
+ for (const warning of getAuthoringWarnings(survey, locale)) {
1315
+ const owner = survey.owners.get(warning.ownerId);
1316
+ located.push({
1317
+ ...warning,
1318
+ severity: "warning",
1319
+ path: owner?.path ?? [],
1320
+ ownerItemId: owner?.itemId
1321
+ });
1322
+ }
1323
+ } catch (error) {
1324
+ if (!(error instanceof DefinitionError)) throw error;
1325
+ }
1326
+ return [...new Map(located.map((issue) => [JSON.stringify([
1327
+ issue.code,
1328
+ issue.path,
1329
+ issue.message
1330
+ ]), issue])).values()];
1331
+ }
1332
+ //#endregion
1333
+ //#region src/engine/session.ts
1334
+ const sessionSnapshotSchema = z.strictObject({
1335
+ schemaVersion: z.literal(1),
1336
+ binding: z.strictObject({
1337
+ sessionId: z.string().min(1),
1338
+ definitionId: z.string().min(1)
1339
+ }),
1340
+ answers: stringRecord(z.string().min(1), responseValueSchema),
1341
+ initialization: z.literal("complete"),
1342
+ navigation: z.strictObject({
1343
+ pageIndex: z.number().int().nonnegative(),
1344
+ viewport: z.enum(["small", "large"])
1345
+ }),
1346
+ randomization: z.strictObject({ seed: z.string().min(1) }),
1347
+ context: z.strictObject({
1348
+ locale: z.string().min(1),
1349
+ /** Defaults to the participant-local day when a new session starts; kept on resume. */
1350
+ currentDate: calendarDaySchema.optional(),
1351
+ participantFlags: stringRecord(z.string(), z.string()).optional(),
1352
+ customValues: stringRecord(z.string(), responseValueSchema).optional()
1353
+ }),
1354
+ events: z.array(surveyEventSchema)
1355
+ });
1356
+ const eventTimestamp = () => Math.floor(Date.now() / 1e3);
1357
+ const comparableAnswer = (value) => JSON.stringify(value, Object.keys(value).sort());
1358
+ function freezeAnswer(value) {
1359
+ if (Array.isArray(value.value)) Object.freeze(value.value);
1360
+ return Object.freeze(value);
1361
+ }
1362
+ function sameRecord(left, right, equal) {
1363
+ const entries = Object.entries(left ?? {});
1364
+ return entries.length === Object.keys(right ?? {}).length && entries.every(([key, value]) => {
1365
+ const other = ownValue(right, key);
1366
+ return other !== void 0 && equal(value, other);
1367
+ });
1368
+ }
1369
+ /** Owns retained answers; participant adapters receive no privileged initialization write path. */
1370
+ var SurveySession = class SurveySession {
1371
+ currentDocument;
1372
+ state;
1373
+ stateGraph;
1374
+ revision = 0;
1375
+ presentationRevision = 0;
1376
+ itemPresentationRevisions = /* @__PURE__ */ new Map();
1377
+ initializationDiagnostics = [];
1378
+ listeners = /* @__PURE__ */ new Set();
1379
+ subscribe = (listener) => {
1380
+ this.listeners.add(listener);
1381
+ return () => {
1382
+ this.listeners.delete(listener);
1383
+ };
1384
+ };
1385
+ getRevision = () => this.revision;
1386
+ /** State/context changes notify every item; wording changes only notify their owning item. */
1387
+ getItemRevision = (itemId) => this.revision - this.presentationRevision + (this.itemPresentationRevisions.get(itemId) ?? 0);
1388
+ /** Prefer the current page, then the next surviving item, then the previous one. */
1389
+ pageAnchors(writtenItems = /* @__PURE__ */ new Set()) {
1390
+ const pages = this.pages();
1391
+ return [
1392
+ ...(pages[this.pageIndex] ?? []).filter((id) => writtenItems.has(id)),
1393
+ ...pages.slice(this.pageIndex).flat(),
1394
+ ...pages.slice(0, this.pageIndex).flat().reverse()
1395
+ ];
1396
+ }
1397
+ reconcilePageAndNotify(anchors) {
1398
+ const pages = this.pages();
1399
+ const pageByItem = new Map(pages.flatMap((items, index) => items.map((id) => [id, index])));
1400
+ const anchoredPage = anchors?.map((id) => pageByItem.get(id)).find((index) => index !== void 0);
1401
+ this.state.navigation.pageIndex = anchoredPage ?? Math.min(this.pageIndex, Math.max(0, pages.length - 1));
1402
+ this.revision++;
1403
+ this.listeners.forEach((listener) => listener());
1404
+ }
1405
+ constructor(currentDocument, state, admission = "strict") {
1406
+ this.currentDocument = currentDocument;
1407
+ this.state = state;
1408
+ this.stateGraph = new SurveyStateGraph(currentDocument, { admission });
1409
+ this.validateAnswers(state.answers);
1410
+ for (const value of Object.values(state.answers)) freezeAnswer(value);
1411
+ for (const value of Object.values(state.context.customValues ?? {})) freezeAnswer(value);
1412
+ }
1413
+ get document() {
1414
+ return this.currentDocument;
1415
+ }
1416
+ /** Wording-only authoring revisions share the compiler indexes and cannot change execution.
1417
+ * Keep the compiled graph, answers, navigation and initialization when updating a live preview.
1418
+ * A structural/configuration revision must create a new session instead.
1419
+ */
1420
+ updatePresentation(document) {
1421
+ if (document === this.currentDocument) return;
1422
+ if (document.elements !== this.currentDocument.elements) throw new Error("Only wording revisions can update a session presentation");
1423
+ const affected = /* @__PURE__ */ new Set();
1424
+ let sharedContentChanged = false;
1425
+ for (const locale of /* @__PURE__ */ new Set([...document.locales, ...this.currentDocument.locales])) {
1426
+ const before = this.currentDocument.data.translations?.[locale];
1427
+ const after = document.data.translations?.[locale];
1428
+ if (before === after) continue;
1429
+ sharedContentChanged ||= before?.navigationContent !== after?.navigationContent || before?.validationMessages !== after?.validationMessages;
1430
+ for (const owner of document.owners.values()) if (before?.content[owner.id] !== after?.content[owner.id]) affected.add(owner.itemId);
1431
+ }
1432
+ if (!sharedContentChanged) {
1433
+ this.presentationRevision++;
1434
+ for (const id of affected) this.itemPresentationRevisions.set(id, (this.itemPresentationRevisions.get(id) ?? 0) + 1);
1435
+ }
1436
+ this.currentDocument = document;
1437
+ this.reconcilePageAndNotify();
1438
+ }
1439
+ static async create(document, input, sources = {}) {
1440
+ if (input.mode === "resumed") {
1441
+ const snapshot = sessionSnapshotSchema.parse(input.snapshot);
1442
+ if (snapshot.binding.sessionId !== input.binding.sessionId || snapshot.binding.definitionId !== input.binding.definitionId) throw new Error("Resume snapshot does not match the session and exact survey definition");
1443
+ const session = new SurveySession(document, snapshot);
1444
+ if (session.pageIndex >= Math.max(1, session.pages().length)) throw new Error("Resume page index is outside the survey");
1445
+ return session;
1446
+ }
1447
+ const session = new SurveySession(document, sessionSnapshotSchema.parse({
1448
+ schemaVersion: 1,
1449
+ binding: input.binding,
1450
+ answers: input.answers ?? {},
1451
+ initialization: "complete",
1452
+ navigation: {
1453
+ pageIndex: 0,
1454
+ viewport: "large"
1455
+ },
1456
+ randomization: { seed: input.seed ?? crypto.randomUUID() },
1457
+ context: {
1458
+ ...input.context,
1459
+ currentDate: input.context.currentDate ?? calendarDateFromLocalDate(/* @__PURE__ */ new Date())
1460
+ },
1461
+ events: [{
1462
+ type: "openSurvey",
1463
+ timestamp: eventTimestamp(),
1464
+ value: input.context.locale
1465
+ }]
1466
+ }), input.admission);
1467
+ await session.initialize(sources);
1468
+ return session;
1469
+ }
1470
+ snapshot() {
1471
+ return structuredClone(this.state);
1472
+ }
1473
+ itemMetadata() {
1474
+ return Object.fromEntries(this.pages().flat().map((itemId, position) => [itemId, { position }]));
1475
+ }
1476
+ active() {
1477
+ return this.stateGraph.evaluate({
1478
+ answers: this.state.answers,
1479
+ context: this.state.context
1480
+ });
1481
+ }
1482
+ get locale() {
1483
+ return this.state.context.locale;
1484
+ }
1485
+ get pageIndex() {
1486
+ return this.state.navigation.pageIndex;
1487
+ }
1488
+ setLocale(locale) {
1489
+ this.setContext({
1490
+ ...this.state.context,
1491
+ locale
1492
+ });
1493
+ }
1494
+ /** Replace host context without restarting initialization or losing retained answers. */
1495
+ setContext(context) {
1496
+ const parsed = sessionSnapshotSchema.shape.context.parse(context);
1497
+ const previous = this.state.context;
1498
+ const next = {
1499
+ ...parsed,
1500
+ currentDate: parsed.currentDate ?? previous.currentDate
1501
+ };
1502
+ if (!next.locale.trim()) throw new Error("A locale is required");
1503
+ const localeChanged = next.locale !== previous.locale;
1504
+ if (!localeChanged && next.currentDate === previous.currentDate && sameRecord(previous.participantFlags, next.participantFlags, (a, b) => a === b) && sameRecord(previous.customValues, next.customValues, (a, b) => JSON.stringify(a) === JSON.stringify(b))) return;
1505
+ for (const value of Object.values(next.customValues ?? {})) freezeAnswer(value);
1506
+ const anchors = this.pageAnchors();
1507
+ this.state = {
1508
+ ...this.state,
1509
+ context: next,
1510
+ events: localeChanged ? [...this.state.events, {
1511
+ type: "languageChanged",
1512
+ timestamp: eventTimestamp(),
1513
+ value: next.locale
1514
+ }] : this.state.events
1515
+ };
1516
+ this.reconcilePageAndNotify(anchors);
1517
+ }
1518
+ orderedIds(ids, ownerId) {
1519
+ return shuffleArray(ids, createSeededRandom(`${this.state.randomization.seed}:${ownerId}`));
1520
+ }
1521
+ setViewport(viewport) {
1522
+ if (viewport === this.state.navigation.viewport) return;
1523
+ const anchors = this.pageAnchors();
1524
+ this.state.navigation.viewport = viewport;
1525
+ this.reconcilePageAndNotify(anchors);
1526
+ }
1527
+ pages(viewport = this.state.navigation.viewport) {
1528
+ const active = this.active(), byId = new Map(this.document.data.surveyItems.map((item) => [item.id, item]));
1529
+ const pages = [[]];
1530
+ const limit = this.document.data.maxItemsPerPage?.[viewport] ?? Infinity;
1531
+ const next = () => {
1532
+ if (pages.at(-1).length) pages.push([]);
1533
+ };
1534
+ const visit = (id) => {
1535
+ const item = byId.get(id);
1536
+ if (!item || !active.visible(id)) return;
1537
+ if (item.itemType === "group") (item.config.shuffleItems ? this.orderedIds(item.config.items, item.id) : item.config.items).forEach(visit);
1538
+ else if (item.itemType === "page-break") next();
1539
+ else {
1540
+ if (pages.at(-1).length >= limit) next();
1541
+ pages.at(-1).push(id);
1542
+ }
1543
+ };
1544
+ visit(this.document.rootId);
1545
+ return pages.filter((page) => page.length);
1546
+ }
1547
+ validate(phase, itemIds) {
1548
+ const result = validateSurveyAnswers(this.stateGraph, this.state.answers, this.state.context, {
1549
+ phase,
1550
+ itemIds
1551
+ });
1552
+ return {
1553
+ ...result,
1554
+ diagnostics: [...this.initializationDiagnostics, ...result.diagnostics]
1555
+ };
1556
+ }
1557
+ goToPage(index, viewport = this.state.navigation.viewport) {
1558
+ const pages = this.pages(viewport);
1559
+ if (!Number.isInteger(index) || index < 0 || index >= pages.length) throw new Error("Invalid page index");
1560
+ if (index > this.pageIndex) {
1561
+ if (this.validate("page", pages[this.pageIndex]).issues.length) throw new Error("Current page has blocking validation errors");
1562
+ }
1563
+ this.state = {
1564
+ ...this.state,
1565
+ navigation: {
1566
+ pageIndex: index,
1567
+ viewport
1568
+ },
1569
+ events: [...this.state.events, {
1570
+ type: "pageChanged",
1571
+ timestamp: eventTimestamp(),
1572
+ value: index
1573
+ }]
1574
+ };
1575
+ this.reconcilePageAndNotify();
1576
+ }
1577
+ submission(responseId) {
1578
+ if (this.validate("submit").issues.length) throw new Error("Survey has blocking validation errors");
1579
+ return structuredClone({
1580
+ schemaVersion: 2,
1581
+ responseId,
1582
+ definitionId: this.state.binding.definitionId,
1583
+ answers: this.active().activeAnswers(),
1584
+ events: this.state.events,
1585
+ itemMetadata: this.itemMetadata(),
1586
+ customValues: this.state.context.customValues,
1587
+ evaluationContext: {
1588
+ locale: this.state.context.locale,
1589
+ currentDate: this.state.context.currentDate,
1590
+ participantFlags: this.state.context.participantFlags
1591
+ },
1592
+ randomization: this.state.randomization
1593
+ });
1594
+ }
1595
+ validateAnswers(answers) {
1596
+ for (const [slotId, value] of Object.entries(answers)) {
1597
+ const slot = this.document.slots.get(slotId);
1598
+ if (!slot || slot.computed) throw new Error(`Unknown or computed answer target '${slotId}'`);
1599
+ const errors = validateResponseSlotValue(slot, value);
1600
+ if (errors.length) throw new Error(errors.join("; "));
1601
+ if (value.type === "reference[]" && new Set(value.value).size !== value.value.length) throw new Error(`Duplicate selected options in '${slotId}'`);
1602
+ }
1603
+ }
1604
+ write(writes) {
1605
+ if (new Set(writes.map((write) => write.slotId)).size !== writes.length) throw new Error("One action cannot write a slot twice");
1606
+ const before = this.active();
1607
+ const changed = writes.filter(({ slotId, value }) => {
1608
+ const previous = ownValue(this.state.answers, slotId);
1609
+ return previous === void 0 || value === void 0 ? previous !== value : comparableAnswer(previous) !== comparableAnswer(value);
1610
+ });
1611
+ for (const { slotId, value } of writes) {
1612
+ const slot = this.document.slots.get(slotId);
1613
+ const binding = this.stateGraph.bindings.get(slotId);
1614
+ if (!slot || slot.computed || !binding) throw new Error(`Slot '${slotId}' is not a writable input`);
1615
+ if (value !== void 0) this.validateAnswers({ [slotId]: value });
1616
+ }
1617
+ for (const { slotId, value } of changed) {
1618
+ const binding = this.stateGraph.bindings.get(slotId);
1619
+ if (!canWriteSelection(binding, ownValue(this.state.answers, slotId), value, before)) throw new Error(`Participant write to '${slotId}' is not permitted in the current state`);
1620
+ }
1621
+ if (!changed.length) return;
1622
+ const anchors = this.pageAnchors(new Set(changed.map(({ slotId }) => this.document.slots.get(slotId).itemId)));
1623
+ const answers = { ...this.state.answers };
1624
+ const timestamp = eventTimestamp();
1625
+ for (const { slotId, value } of changed) if (value === void 0) delete answers[slotId];
1626
+ else setOwnValue(answers, slotId, freezeAnswer(structuredClone(value)));
1627
+ const events = [...this.state.events];
1628
+ for (const { slotId, value } of changed) {
1629
+ const event = {
1630
+ type: "responseChanged",
1631
+ slotId,
1632
+ operation: value === void 0 ? "clear" : "set",
1633
+ timestamp
1634
+ };
1635
+ const previous = events.at(-1);
1636
+ if (previous?.type === event.type && previous.slotId === slotId && previous.operation === event.operation) events[events.length - 1] = event;
1637
+ else events.push(event);
1638
+ }
1639
+ this.state = {
1640
+ ...this.state,
1641
+ answers,
1642
+ events
1643
+ };
1644
+ this.reconcilePageAndNotify(anchors);
1645
+ }
1646
+ evaluate(expression) {
1647
+ const active = this.active();
1648
+ return evaluateExpression(expression, {
1649
+ ...this.state.context,
1650
+ answer: active.answer,
1651
+ disabled: active.disabled
1652
+ });
1653
+ }
1654
+ async initialize(sources) {
1655
+ const prefills = new Map([...this.document.elements.values()].flatMap((element) => Object.entries(element.node.prefills ?? {})));
1656
+ for (const slotId of this.stateGraph.initializationOrder) {
1657
+ if (Object.hasOwn(this.state.answers, slotId)) continue;
1658
+ for (const [index, candidate] of (prefills.get(slotId) ?? []).entries()) {
1659
+ const path = [
1660
+ ...this.document.elements.get(this.document.slots.get(slotId).elementId).path,
1661
+ "prefills",
1662
+ slotId,
1663
+ index
1664
+ ];
1665
+ const warn = (message) => this.initializationDiagnostics.push({
1666
+ code: "prefill-value",
1667
+ severity: "warning",
1668
+ path,
1669
+ message
1670
+ });
1671
+ const evaluate = (expression) => {
1672
+ try {
1673
+ return this.evaluate(expression);
1674
+ } catch (error) {
1675
+ if (!(error instanceof ExpressionEvaluationError)) throw error;
1676
+ warn(`Prefill '${candidate.id}' was skipped: ${error.message}`);
1677
+ return;
1678
+ }
1679
+ };
1680
+ if (candidate.when) {
1681
+ const result = evaluate(candidate.when);
1682
+ if (result?.type !== "boolean" || !result.value) continue;
1683
+ }
1684
+ let value;
1685
+ switch (candidate.source.type) {
1686
+ case "static":
1687
+ value = candidate.source.value;
1688
+ break;
1689
+ case "expression":
1690
+ value = evaluate(candidate.source.expression);
1691
+ break;
1692
+ case "templateValue": {
1693
+ const template = ownValue(this.document.data.templateValues, candidate.source.key);
1694
+ if (template?.expression) {
1695
+ value = evaluate(template.expression);
1696
+ if (template.type === "formatDate" && value?.type === "date") value = {
1697
+ type: "string",
1698
+ value: formatCalendarDate(value.value, template.dateFormat)
1699
+ };
1700
+ }
1701
+ break;
1702
+ }
1703
+ case "previousResponse":
1704
+ if (!sources.previousResponse && candidate.source.ifUnsupported !== "skip") throw new Error("This host does not supply previous responses for prefills");
1705
+ value = await sources.previousResponse?.(candidate.source.ref);
1706
+ }
1707
+ if (value === void 0) continue;
1708
+ if (candidate.source.type === "previousResponse") {
1709
+ const parsed = responseValueSchema.safeParse(value);
1710
+ const slot = this.document.slots.get(slotId);
1711
+ if (parsed.success && parsed.data.type === "reference[]" && slot.valueType === "reference[]" && slot.allowedValues) {
1712
+ const allowed = new Set(slot.allowedValues.flatMap(responseValueElements).flatMap((option) => option.type === "reference" ? [option.value] : []));
1713
+ const retained = parsed.data.value.filter((id) => allowed.has(id));
1714
+ if (retained.length !== parsed.data.value.length) {
1715
+ warn(`Prefill '${candidate.id}' discarded options outside the current response domain`);
1716
+ if (!retained.length) continue;
1717
+ value = {
1718
+ type: "reference[]",
1719
+ value: retained
1720
+ };
1721
+ }
1722
+ }
1723
+ }
1724
+ const issues = this.document.slots.validateAnswers({ [slotId]: value });
1725
+ if (issues.length) {
1726
+ warn(`Prefill '${candidate.id}' was skipped: ${issues.map((issue) => issue.message).join("; ")}`);
1727
+ continue;
1728
+ }
1729
+ setOwnValue(this.state.answers, slotId, freezeAnswer(structuredClone(value)));
1730
+ break;
1731
+ }
1732
+ }
1733
+ }
1734
+ };
1735
+ //#endregion
968
1736
  //#region src/response-exporter/csv-serializer.ts
969
1737
  const DEFAULT_OPTIONS = {
970
1738
  separator: ",",
@@ -999,6 +1767,13 @@ function serializeToCsv(columns, rows, options) {
999
1767
  return lines.join(opts.lineEnding);
1000
1768
  }
1001
1769
  //#endregion
1770
+ //#region src/response-exporter/validate-configuration.ts
1771
+ /** Export requires an executable definition, including complete expression operands. */
1772
+ function validateExportConfiguration(survey, registry = survey.slots) {
1773
+ registry.assertValid();
1774
+ new SurveyStateGraph(survey);
1775
+ }
1776
+ //#endregion
1002
1777
  //#region src/response-exporter/exporter.ts
1003
1778
  const canonical = (value) => JSON.stringify(value, (_key, entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a.localeCompare(b))) : entry);
1004
1779
  /** Export only against schemas selected by the host, never against response version tags. */
@@ -1038,18 +1813,18 @@ var SurveyResponseExporter = class SurveyResponseExporter {
1038
1813
  }
1039
1814
  compileContexts() {
1040
1815
  return new Map(this.batches.map((batch) => {
1041
- const registry = batch.schema.getResponseSlotRegistry();
1816
+ const registry = batch.schema.slots;
1042
1817
  validateExportConfiguration(batch.schema, registry);
1043
- const slots = new Map(registry.definitions.map((slot) => [slot.slotId, slot]));
1818
+ const slots = new Map(localizedExportSlots(batch.schema).map((slot) => [slot.slotId, slot]));
1044
1819
  const profile = this.profile(batch);
1045
1820
  const transforms = new Map([...slots.values()].map((slot) => [slot.slotId, {
1046
1821
  ...DEFAULT_TRANSFORM,
1047
1822
  ...slot.exportSettings,
1048
- ...profile?.slotOverrides?.[slot.slotId]
1823
+ ...ownValue(profile?.slotOverrides, slot.slotId)
1049
1824
  }]));
1050
1825
  const columns = [...slots.values()].flatMap((slot) => projectSlot(slot, transforms.get(slot.slotId)).map((column) => ({
1051
1826
  ...column,
1052
- header: profile?.columnHeaders?.[column.id]?.trim() ?? column.header
1827
+ header: ownValue(profile?.columnHeaders, column.id)?.trim() ?? column.header
1053
1828
  })));
1054
1829
  const encodings = /* @__PURE__ */ new Map();
1055
1830
  for (const slotId of new Set(columns.map((column) => column.slotId))) {
@@ -1064,7 +1839,7 @@ var SurveyResponseExporter = class SurveyResponseExporter {
1064
1839
  encodings,
1065
1840
  columns,
1066
1841
  included: new Set(columns.map((column) => column.id)),
1067
- activePlan: columns.some((column) => slots.get(column.slotId)?.computed) ? new ActiveResponsePlan(batch.schema) : void 0
1842
+ stateGraph: new SurveyStateGraph(batch.schema)
1068
1843
  }];
1069
1844
  }));
1070
1845
  }
@@ -1072,7 +1847,7 @@ var SurveyResponseExporter = class SurveyResponseExporter {
1072
1847
  if (!profile) return;
1073
1848
  if (profile.schemaVersion !== 1) throw new Error(`Unsupported exporter profile schema version: ${String(profile.schemaVersion)}`);
1074
1849
  const slots = new Map(batches.flatMap((batch) => [...contexts.get(batch).slots]));
1075
- const contextKeys = new Set([...batches.flatMap((batch) => batch.responses), ...extraResponses].flatMap((response) => Object.keys(response.context ?? {})));
1850
+ const contextKeys = new Set([...batches.flatMap((batch) => batch.responses), ...extraResponses].flatMap((response) => Object.keys(response.customValues ?? {})));
1076
1851
  const columns = /* @__PURE__ */ new Set([
1077
1852
  ...[...slots.values()].flatMap((slot) => projectSlot(slot, { mode: "default" }, true).map((column) => column.id)),
1078
1853
  ...META_COLUMN_ORDER.map((field) => encodeExportColumnId(["meta", field])),
@@ -1108,7 +1883,7 @@ var SurveyResponseExporter = class SurveyResponseExporter {
1108
1883
  const signature = canonical({
1109
1884
  type: slot.valueType,
1110
1885
  allowedValues: slot.allowedValues?.map((value) => canonical(value)).sort(),
1111
- codes: this.options.codingPolicy === "harmonized" ? context.profile?.slotOverrides?.[slot.slotId]?.optionCodes ?? slot.referenceValueKeys : slot.referenceValueKeys,
1886
+ codes: this.options.codingPolicy === "harmonized" ? ownValue(context.profile?.slotOverrides, slot.slotId)?.optionCodes ?? slot.referenceValueKeys : slot.referenceValueKeys,
1112
1887
  computed: slot.computed,
1113
1888
  scores: slot.referenceValueScores
1114
1889
  });
@@ -1127,7 +1902,7 @@ var SurveyResponseExporter = class SurveyResponseExporter {
1127
1902
  });
1128
1903
  const profile = this.options.profile ?? (this.batches.length === 1 ? this.batches[0].profile : void 0);
1129
1904
  const allResponses = [...this.batches.flatMap((batch) => batch.responses), ...extraResponses];
1130
- const contextKeys = [...new Set(allResponses.flatMap((response) => Object.keys(response.context ?? {})))];
1905
+ const contextKeys = [...new Set(allResponses.flatMap((response) => Object.keys(response.customValues ?? {})))];
1131
1906
  const ordered = (values, order, id, name) => values.sort((a, b) => {
1132
1907
  const left = order.indexOf(id(a)), right = order.indexOf(id(b));
1133
1908
  if (left >= 0 || right >= 0) return (left < 0 ? Infinity : left) - (right < 0 ? Infinity : right);
@@ -1160,23 +1935,18 @@ var SurveyResponseExporter = class SurveyResponseExporter {
1160
1935
  const diagnostics = registry.validateAnswers(response.answers);
1161
1936
  if (diagnostics.length) throw new Error(diagnostics.map((issue) => issue.message).join("\n"));
1162
1937
  const row = Object.create(null);
1163
- const recorded = Object.create(null);
1164
- if (context.activePlan) for (const [slotId, value] of Object.entries(response.answers)) {
1165
- const slot = slots.get(slotId);
1166
- const item = batch.schema.surveyItems.get(slot.itemId);
1167
- (recorded[slot.itemId] ??= new SurveyItemResponse({
1168
- itemId: slot.itemId,
1169
- itemType: item.type
1170
- }, new ResponseItem())).response.setSlotValue(slotId, structuredClone(value));
1171
- }
1172
- const active = context.activePlan?.evaluate(recorded, {
1173
- locale: response.evaluationContext?.locale ?? batch.schema.locales[0] ?? "en",
1174
- participantFlags: response.evaluationContext?.participantFlags,
1175
- customValues: response.context
1938
+ const active = context.stateGraph.evaluate({
1939
+ answers: response.answers,
1940
+ context: {
1941
+ locale: response.evaluationContext?.locale ?? batch.schema.locales[0] ?? "en",
1942
+ currentDate: response.evaluationContext?.currentDate,
1943
+ participantFlags: response.evaluationContext?.participantFlags,
1944
+ customValues: response.customValues
1945
+ }
1176
1946
  });
1177
1947
  for (const column of columns) if (column.source === ExportColumnSource.meta) switch (column.metaField) {
1178
1948
  case "responseId":
1179
- row[column.header] = response.key;
1949
+ row[column.header] = response.responseId;
1180
1950
  break;
1181
1951
  case "participantId":
1182
1952
  row[column.header] = response.participantId ?? "";
@@ -1187,18 +1957,18 @@ var SurveyResponseExporter = class SurveyResponseExporter {
1187
1957
  case "submittedAt": row[column.header] = response.submittedAt ? format(/* @__PURE__ */ new Date(response.submittedAt * 1e3), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx") : "";
1188
1958
  }
1189
1959
  else if (column.source === ExportColumnSource.context) {
1190
- const value = response.context?.[column.contextKey];
1960
+ const value = ownValue(response.customValues, column.contextKey);
1191
1961
  row[column.header] = exportSingleResponseSlot({
1192
1962
  value,
1193
1963
  valueType: value?.type ?? ValueType.string,
1194
- transformConfig: profile?.contextOverrides?.[column.contextKey] ?? {
1964
+ transformConfig: ownValue(profile?.contextOverrides, column.contextKey) ?? {
1195
1965
  ...DEFAULT_TRANSFORM,
1196
1966
  categorical: "ids"
1197
1967
  }
1198
1968
  });
1199
1969
  } else {
1200
1970
  const slot = slots.get(column.slotId);
1201
- row[column.header] = slot && included.has(column.id) ? formatProjectedValue(slot, column, slot.computed ? active?.getAnswer(slot.slotId) : response.answers[slot.slotId], transforms.get(slot.slotId)) : "";
1971
+ row[column.header] = slot && included.has(column.id) ? formatProjectedValue(slot, column, active.answer(slot.slotId), transforms.get(slot.slotId)) : "";
1202
1972
  }
1203
1973
  return row;
1204
1974
  }
@@ -1228,16 +1998,16 @@ var SurveyResponseExporter = class SurveyResponseExporter {
1228
1998
  * Generate a structured codebook from a survey definition.
1229
1999
  */
1230
2000
  function generateCodebook(survey, options) {
1231
- const registry = survey.getResponseSlotRegistry();
2001
+ const registry = survey.slots;
1232
2002
  validateExportConfiguration(survey, registry);
1233
- const slots = registry.definitions;
2003
+ const slots = localizedExportSlots(survey);
1234
2004
  const locales = options?.locales ?? survey.locales;
1235
2005
  const slotDefsByItemId = /* @__PURE__ */ new Map();
1236
2006
  for (const slot of slots) {
1237
2007
  const entries = slotDefsByItemId.get(slot.itemId) ?? [];
1238
2008
  entries.push({
1239
2009
  slotId: slot.slotId,
1240
- primitiveId: slot.primitiveId,
2010
+ elementId: slot.elementId,
1241
2011
  ...slot.rowId ? { rowId: slot.rowId } : {},
1242
2012
  ...slot.columnId ? { columnId: slot.columnId } : {},
1243
2013
  variableName: slot.variableName,
@@ -1245,45 +2015,47 @@ function generateCodebook(survey, options) {
1245
2015
  encoding: resolvedSlotEncoding(slot, {
1246
2016
  mode: "default",
1247
2017
  ...slot.exportSettings,
1248
- ...options?.profile?.slotOverrides?.[slot.slotId]
2018
+ ...ownValue(options?.profile?.slotOverrides, slot.slotId)
1249
2019
  }),
1250
2020
  optionLabels: slot.referenceValueLabels,
1251
- allowedValues: slot.allowedValues,
1252
- ...slot.computed ? { computed: structuredClone(slot.computed) } : {},
2021
+ ...slot.allowedValues ? { allowedValues: slot.allowedValues.map((value) => structuredClone(value)) } : {},
2022
+ ...slot.computed ? { computed: {
2023
+ ...slot.computed,
2024
+ sourceSlotIds: [...slot.computed.sourceSlotIds]
2025
+ } } : {},
1253
2026
  ...slot.referenceValueKeys ? { referenceValueKeys: { ...slot.referenceValueKeys } } : {},
1254
2027
  ...slot.referenceValueScores ? { referenceValueScores: { ...slot.referenceValueScores } } : {}
1255
2028
  });
1256
2029
  slotDefsByItemId.set(slot.itemId, entries);
1257
2030
  }
1258
2031
  const items = [];
1259
- for (const item of survey.surveyItems.values()) {
2032
+ for (const item of survey.data.surveyItems) {
1260
2033
  const slotList = slotDefsByItemId.get(item.id) ?? [];
1261
2034
  slotList.sort((a, b) => a.slotId.localeCompare(b.slotId));
1262
2035
  let contentByLocale;
1263
2036
  if (locales.length > 0) {
1264
2037
  contentByLocale = {};
1265
- const itemTranslations = survey.getItemTranslations(item.id);
1266
- if (itemTranslations) for (const locale of locales) {
1267
- const all = itemTranslations.getAllForLocale(locale);
1268
- if (all) {
1269
- const flat = {};
1270
- for (const [contentKey, content] of Object.entries(all)) flat[contentKey] = getContentPlainText(content);
1271
- contentByLocale[locale] = flat;
2038
+ for (const locale of locales) {
2039
+ const values = {};
2040
+ for (const owner of survey.owners.values()) if (owner.itemId === item.id) {
2041
+ const roles = ownValue(ownValue(survey.data.translations, locale)?.content, owner.id);
2042
+ if (roles) setOwnValue(values, owner.id, Object.fromEntries(Object.entries(roles).map(([role, content]) => [role, getContentPlainText(content)])));
1272
2043
  }
2044
+ setOwnValue(contentByLocale, locale, values);
1273
2045
  }
1274
2046
  }
1275
2047
  items.push({
1276
2048
  id: item.id,
1277
2049
  editorName: survey.resolveItemEditorName(item.id, locales[0]),
1278
2050
  breadcrumb: survey.getItemDisplayPath(item.id, locales[0]).breadcrumb,
1279
- type: item.type,
1280
- interactive: item.isInteractive(),
2051
+ type: item.itemType,
2052
+ interactive: slotList.some((slot) => !slot.computed),
1281
2053
  slots: slotList,
1282
2054
  contentByLocale
1283
2055
  });
1284
2056
  }
1285
2057
  return {
1286
- surveyKey: survey.surveyKey,
2058
+ surveyKey: survey.data.metadata?.surveyKey,
1287
2059
  items,
1288
2060
  projection: new SurveyResponseExporter([{
1289
2061
  schema: survey,
@@ -1307,18 +2079,42 @@ function generateExportCodebook(batches, options = {}) {
1307
2079
  sourceId: batch.sourceId,
1308
2080
  surveySchemaVersion: batch.schema.serialize().schemaVersion,
1309
2081
  profile: batch.profile && structuredClone(batch.profile),
1310
- variables: batch.schema.getResponseSlotRegistry().definitions.map((slot) => ({
2082
+ variables: localizedExportSlots(batch.schema).map((slot) => ({
1311
2083
  ...slot,
1312
2084
  encoding: resolvedSlotEncoding(slot, {
1313
2085
  mode: "default",
1314
2086
  ...slot.exportSettings,
1315
- ...options.profile?.slotOverrides?.[slot.slotId] ?? batch.profile?.slotOverrides?.[slot.slotId]
2087
+ ...ownValue(options.profile?.slotOverrides, slot.slotId) ?? ownValue(batch.profile?.slotOverrides, slot.slotId)
1316
2088
  })
1317
2089
  }))
1318
2090
  }))
1319
2091
  };
1320
2092
  }
1321
2093
  //#endregion
1322
- export { ActiveResponsePlan, AndExpressionEditor, CURRENT_SURVEY_SCHEMA, CURRENT_SURVEY_SCHEMA_VERSION, ConstBooleanEditor, ConstDateArrayEditor, ConstDateEditor, ConstExpression, ConstNumberArrayEditor, ConstNumberEditor, ConstStringArrayEditor, ConstStringEditor, ContentType, ContextVariableExpression, ContextVariableType, CtxCustomExpressionEditor, CtxCustomValueEditor, CtxLocaleEditor, CtxPFlagDateEditor, CtxPFlagIsDefinedEditor, CtxPFlagNumEditor, CtxPFlagStringEditor, DATE_FUNCTION_SIGNATURES, DEFAULT_TRANSFORM, DurationUnits, EXPORTER_PROFILE_SCHEMA_VERSION, EqExpressionEditor, Expression, ExpressionEditor, ExpressionEvaluator, ExpressionType, FunctionExpression, FunctionExpressionNames, GroupItemCore, GtExpressionEditor, GteExpressionEditor, InRangeExpressionEditor, JsonPatchError, LtExpressionEditor, LteExpressionEditor, META_COLUMN_ORDER, MaxExpressionEditor, MinExpressionEditor, NumberPrecision, OrExpressionEditor, PageBreakItemCore, RESPONSE_VARIABLE_NAME_MAX_LENGTH, ReferenceUsageType, ReservedSurveyItemTypes, ResponseItem, ResponseSlotRegistry, ResponseVariableEditor, ResponseVariableExpression, SURVEY_EDITOR_ITEM_COLORS, SURVEY_EXPRESSION_REFERENCE_DIAGNOSTIC_POLICY, SURVEY_RESPONSE_SCHEMA_VERSION, SlotTransformMode, StrEqExpressionEditor, StrListContainsExpressionEditor, SumExpressionEditor, Survey, SurveyEngineCore, SurveyEventTypes, SurveyItemCore, SurveyItemPrefillApplyMode, SurveyItemPrefillTargetType, SurveyItemResponse, SurveyItemTranslations, SurveyResponse, SurveyResponseExporter, SurveyTranslations, TemplateDefTypes, ValueReference, ValueReferenceMethod, ValueType, addCalendarDate, allocateResponseVariableName, analyzeSurveyExpressionReferences, and, applyJsonPatch, assertResponseValue, buildItemExpression, builtInItemCoreRegistry, calendarDateBounds, calendarDateFromLocalDate, calendarDatePrecision, calendarDateToLocalDate, collectConfigValueExpressions, collectExpressionResponseReferences, collectSurveyExpressionLocations, compareCalendarDates, const_boolean, const_date, const_date_array, const_number, const_number_array, const_string, const_string_array, createFullRegistry, createItemCore, createItemTypeDefinitionRegistry, createRichTextContent, createSeededRandom, ctx_custom_expression, ctx_custom_value, ctx_locale, ctx_pflag_date, ctx_pflag_is_defined, ctx_pflag_num, ctx_pflag_string, date_add_days, date_add_months, date_add_years, date_diff, date_eq, date_gt, date_gte, date_lt, date_lte, date_max, date_min, decodeExportColumnId, decodeJsonPointerSegment, deserializeSurveyItemPrefill, deserializeTemplateValue, deserializeTemplateValues, differenceCalendarDates, encodeExportColumnId, encodeJsonPointerSegment, eq, escapeCsvCell, exportSingleResponseSlot, expressionReferenceDiagnosticKey, findIntroducedBlockingExpressionReferenceDiagnostics, findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories, flattenTree, formatCalendarDate, formatProjectedValue, generateCodebook, generateCodingKey, generateExportCodebook, generateId, getAssetUsagesFromContent, getCompositionLabel, getContentPlainText, getDefaultProjectionIssues, getItemExpressionDefinition, getPlainTextFromRichTextContent, getValueAtJsonPointer, gt, gte, hasRenderableRichTextBlock, hasRenderableRichTextContent, in_range, initValueForType, invalidResponseDomainValues, isBlockingSurveyExpressionReferenceDiagnostic, isBuiltInItemType, isCalendarDate, isContentEmpty, isLegacyItemGroupComponent, isLegacySurveyGroupItem, isResponseValue, isValidResponseVariableName, lt, lte, max, min, optionMapping, or, parseJsonPointer, prefillTargetsEqual, projectSlot, replaceResponseVariableReferences, resolveUniqueItemKey, responseValueElements, responseValueReferenceIds, responseValuesEqual, response_boolean, response_date, response_date_array, response_number, response_number_array, response_string, response_string_array, serializeSurveyItemPrefill, serializeTemplateValue, serializeTemplateValues, serializeToCsv, shuffleArray, shuffleIndices, str_eq, str_list_contains, structuredCloneMethod, suggestResponseVariableName, sum, toItemTypeDefinitionRegistry, validateLocale, validateResponseSlotValue };
2094
+ //#region src/engine/submission.ts
2095
+ /** The host chooses the exact definition and trusted context before calling this boundary. */
2096
+ function validateSurveySubmission(survey, input, context, binding) {
2097
+ const response = SurveyResponse.deserialize(input);
2098
+ if (response.definitionId !== binding.definitionId) throw new Error(`Response was given against definition '${response.definitionId}', not '${binding.definitionId}'`);
2099
+ const issues = survey.slots.validateAnswers(response.answers);
2100
+ if (issues.length) throw new Error(issues.map((issue) => issue.message).join("\n"));
2101
+ const plan = new SurveyStateGraph(survey);
2102
+ const applicable = plan.evaluate({
2103
+ answers: response.answers,
2104
+ context
2105
+ }).activeAnswers();
2106
+ const validation = validateSurveyAnswers(plan, applicable, context, { phase: "submit" });
2107
+ if (validation.issues.length) throw new Error(validation.issues.map((issue) => `${issue.slotId}: ${issue.code}`).join("\n"));
2108
+ response.answers = structuredClone(applicable);
2109
+ response.evaluationContext = {
2110
+ locale: context.locale,
2111
+ currentDate: context.currentDate,
2112
+ participantFlags: context.participantFlags && { ...context.participantFlags }
2113
+ };
2114
+ response.customValues = context.customValues && structuredClone(context.customValues);
2115
+ return response;
2116
+ }
2117
+ //#endregion
2118
+ export { AndExpressionEditor, BooleanExportValues, CALENDAR_DATE_UNITS, CURRENT_SURVEY_SCHEMA, CURRENT_SURVEY_SCHEMA_VERSION, ConstBooleanEditor, ConstDateArrayEditor, ConstDateEditor, ConstNumberArrayEditor, ConstNumberEditor, ConstStringArrayEditor, ConstStringEditor, ContentType, CtxCurrentDateEditor, CtxCustomValueEditor, CtxLocaleEditor, CtxPFlagDateEditor, CtxPFlagIsDefinedEditor, CtxPFlagNumEditor, CtxPFlagStringEditor, DEFAULT_TRANSFORM, DefinitionError, DisabledStateEditor, DurationUnits, EXPORTER_PROFILE_SCHEMA_VERSION, EqExpressionEditor, ExportColumnSource, ExpressionEditor, ExpressionEvaluationError, FUNCTION_SIGNATURES, GtExpressionEditor, GteExpressionEditor, InRangeExpressionEditor, JsonPatchError, LtExpressionEditor, LteExpressionEditor, META_COLUMN_ORDER, MaxExpressionEditor, MinExpressionEditor, NumberPrecision, OrExpressionEditor, RESPONSE_VARIABLE_NAME_MAX_LENGTH, ResponseSlotRegistry, ResponseVariableEditor, SURVEY_LOCALIZATION_SURFACES, SURVEY_RESPONSE_SCHEMA_VERSION, SlotTransformMode, StrEqExpressionEditor, StrListContainsExpressionEditor, SumExpressionEditor, Survey, SurveyEventTypes, SurveyResponse, SurveyResponseExporter, SurveySession, SurveyStateGraph, ValueType, addCalendarDate, allocateResponseVariableName, and, annotated, annotatedReferences, applyJsonPatch, assertResponseValue, boundField, calendarDateBounds, calendarDateFromLocalDate, calendarDatePrecision, calendarDateToLocalDate, calendarDaySchema, canWriteSelection, checkDocumentBudget, collectExpressionDependencies, compareCalendarDates, conditionsShape, const_boolean, const_date, const_date_array, const_number, const_number_array, const_string, const_string_array, contentOwner, contentOwnerIds, createCompositionSchema, createElementRegistry, createFlowItemSchema, createFunctionExpression, createRichTextContent, createSeededRandom, createSurveySchema, ctx_current_date, ctx_custom_value, ctx_locale, ctx_pflag_date, ctx_pflag_is_defined, ctx_pflag_num, ctx_pflag_string, date_add_days, date_add_months, date_add_years, date_diff, date_eq, date_gt, date_gte, date_lt, date_lte, date_max, date_min, decodeExportColumnId, decodeJsonPointerSegment, defineElement, differenceCalendarDates, elementShape, encodeExportColumnId, encodeJsonPointerSegment, eq, escapeCsvCell, evaluateExpression, exportSingleResponseSlot, expressionArguments, expressionEditorConfigSchema, expressionField, expressionIssues, expressionSchema, expressionTypeIssues, extendObject, followUpBody, formatCalendarDate, formatProjectedValue, functionNameSchema, functionTypeIssues, generateCodebook, generateCodingKey, generateExportCodebook, generateId, getAssetUsagesFromContent, getAuthoringWarnings, getBrokenReferences, getCompositionLabel, getContentPlainText, getDefaultProjectionDiagnostics, getDefaultProjectionIssues, getDefinitionDiagnostics, getElementSemanticContext, getLocalizationCellKey, getLocalizationCoverage, getLocalizationEntryKey, getLocalizationItems, getPlainTextFromRichTextContent, getSessionDiagnostics, getValueAtJsonPointer, gt, gte, hasLocalizationText, hasPermittedChange, hasRenderableRichTextBlock, hasRenderableRichTextContent, if_else, in_range, inferExpressionReturnType, inspectAnnotated, invalidResponseDomainValues, isAdmissibleInDraft, isCalendarDate, isCalendarDateUnit, isContentEmpty, isEmptySurveyLocalizationEntry, isResponseValue, isValidResponseVariableName, is_disabled, itemEditorMetadataSchema, itemResponseMetadataSchema, jsonValueSchema, layoutPolicySchema, listLocalizationEntries, list_count, localizedExportSlots, lt, lte, max, min, moveArrayElement, operand, optionIds, optionMapping, or, ownValue, ownedId, ownerAtPath, parseJsonPointer, projectSlot, readLocalizationContent, referenceId, remapAnnotated, remapExpression, resolveElementName, resolveParticipantContent, resolveUniqueItemKey, resolvedSlotEncoding, responseValueElements, responseValueSchema, responseValuesEqual, response_boolean, response_date, response_date_array, response_number, response_number_array, response_string, response_string_array, richTextImageDimensionSchema, richTextImageSourceSchema, schemaAnnotation, selectedAndEnabled, serializeToCsv, sessionSnapshotSchema, setOwnValue, shuffleArray, shuffleIndices, slotPrefillSchema, slotTransformSchema, str_eq, str_list_contains, strictKeyedObject, stringRecord, structuredCloneMethod, suggestResponseVariableName, sum, surveyContentSchema, surveyEventSchema, surveyLocales, surveyResponseSchema, swapArrayElements, templateValueSchema, transformAnnotated, transformExpression, unreachable, validateLocalizationRef, validateResponseSlotValue, validateSurveyAnswers, validateSurveySubmission, validationRuleSchema, valueTypeSchema, visitExpression };
1323
2119
 
1324
2120
  //# sourceMappingURL=index.mjs.map