@micha.bigler/survey-renderer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,985 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import KeyboardArrowDownRoundedIcon from "@mui/icons-material/KeyboardArrowDownRounded";
4
+ import KeyboardArrowUpRoundedIcon from "@mui/icons-material/KeyboardArrowUpRounded";
5
+ import { Alert, Autocomplete, Box, Button, Card, Checkbox, Chip, IconButton, MenuItem, CircularProgress, Divider, LinearProgress, Paper, Radio, RadioGroup, Rating, Slider, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, } from "@mui/material";
6
+ import { useTranslation } from "react-i18next";
7
+ import { interpolateSurveyText, localizeSurveyValue } from "./localizeSurvey";
8
+ import { resolveQuestionUiConfig } from "./questionUiConfig";
9
+ import { getGroupsByName, getPageStagedGroupKey, getQuestionStagedGroupKey, getStagedQuestionNames, isQuestionVisible, } from "./stagedGroups";
10
+ import SurveyQuestionHeader, { renderRichText } from "./SurveyQuestionHeader";
11
+ import SurveyDiscoverySelect from "./SurveyDiscoverySelect";
12
+ import SurveyFilteredPlaceSelect from "./SurveyFilteredPlaceSelect";
13
+ import SurveyGeoSelect from "./SurveyGeoSelect";
14
+ import SurveyMapSelect from "./SurveyMapSelect";
15
+ function formatQuestionText(text) {
16
+ const source = (text || "").trim();
17
+ const exactBoldMatch = source.match(/^\*\*(.+)\*\*$/);
18
+ if (exactBoldMatch) {
19
+ return { kind: "title", text: exactBoldMatch[1].trim() };
20
+ }
21
+ return { kind: "body", text: source };
22
+ }
23
+ function parseTitledContent(text) {
24
+ const source = (text || "").trim();
25
+ const titledMatch = source.match(/^\*\*(.+?)\*\*\s*([\s\S]*)$/);
26
+ if (!titledMatch) {
27
+ return null;
28
+ }
29
+ return {
30
+ title: titledMatch[1].trim(),
31
+ body: titledMatch[2].trim(),
32
+ };
33
+ }
34
+ function normalizeTitle(text) {
35
+ return String(text || "")
36
+ .toLowerCase()
37
+ .replace(/\(.*?\)/g, "")
38
+ .replace(/[^a-z0-9]+/g, " ")
39
+ .trim();
40
+ }
41
+ function shouldHideNoteTitle(noteTitle, surveyTitle) {
42
+ const normalizedNoteTitle = normalizeTitle(noteTitle);
43
+ const normalizedSurveyTitle = normalizeTitle(surveyTitle);
44
+ if (!normalizedNoteTitle || !normalizedSurveyTitle) {
45
+ return false;
46
+ }
47
+ return (normalizedNoteTitle === normalizedSurveyTitle ||
48
+ normalizedNoteTitle.includes(normalizedSurveyTitle) ||
49
+ normalizedSurveyTitle.includes(normalizedNoteTitle));
50
+ }
51
+ function isTitleNote(question, language, contextAnswers = {}) {
52
+ if (!question || question.type !== "note") {
53
+ return false;
54
+ }
55
+ const label = interpolateSurveyText(localizeSurveyValue(question.labels, language), contextAnswers);
56
+ return /^\*\*(.+)\*\*$/.test((label || "").trim());
57
+ }
58
+ function ChoiceCardGroup({ imageChoice = false, maxSelections, mediaUrl, multiple = false, neutral = false, onChange, onOtherChange, options, otherOptionValue, otherValue, renderOptionOverlay, value }) {
59
+ const { t } = useTranslation();
60
+ const [failedImageValues, setFailedImageValues] = React.useState(() => new Set());
61
+ const selectedValues = multiple ? (Array.isArray(value) ? value : []) : [value];
62
+ const maximum = Number(maxSelections);
63
+ const hasMaximum = multiple && Number.isInteger(maximum) && maximum > 0;
64
+ const maximumReached = hasMaximum && selectedValues.length >= maximum;
65
+ const useResponsiveColumns = options.length > 1;
66
+ const choiceCards = options.map((option) => {
67
+ const isSelected = multiple
68
+ ? selectedValues.includes(option.value)
69
+ : value === option.value;
70
+ const isDisabled = maximumReached && !isSelected;
71
+ const isOtherOption = option.value === otherOptionValue;
72
+ const toggleOption = () => {
73
+ if (isDisabled)
74
+ return;
75
+ if (multiple) {
76
+ const nextValue = isSelected
77
+ ? selectedValues.filter((entry) => entry !== option.value)
78
+ : [...selectedValues, option.value];
79
+ onChange(nextValue);
80
+ return;
81
+ }
82
+ onChange(isSelected ? "" : option.value);
83
+ };
84
+ const card = (_jsx(Paper, { "aria-checked": isSelected, "aria-disabled": isDisabled || undefined, "aria-label": imageChoice && option.label ? option.label : undefined, onClick: toggleOption, onKeyDown: (event) => {
85
+ if (event.key === "Enter" || event.key === " ") {
86
+ if (event.key === " ") {
87
+ event.preventDefault();
88
+ }
89
+ toggleOption();
90
+ }
91
+ }, role: multiple ? "checkbox" : "radio", sx: {
92
+ "&:focus-visible": {
93
+ outline: "2px solid",
94
+ outlineColor: "primary.main",
95
+ outlineOffset: 2,
96
+ },
97
+ backgroundColor: neutral ? "background.paper" : "rgba(255, 243, 249, 0.96)",
98
+ border: "1px solid",
99
+ borderColor: isSelected ? "primary.main" : "divider",
100
+ borderRadius: 2,
101
+ cursor: isDisabled ? "not-allowed" : "pointer",
102
+ minHeight: 72,
103
+ px: 1.5,
104
+ py: 1.1,
105
+ transition: "border-color 150ms ease, background-color 150ms ease",
106
+ opacity: isDisabled ? 0.52 : 1,
107
+ bgcolor: isSelected ? (neutral ? "action.selected" : "rgba(13, 174, 209, 0.16)") : (neutral ? "background.paper" : "rgba(255, 243, 249, 0.96)"),
108
+ }, tabIndex: isDisabled ? -1 : 0, children: _jsxs(Stack, { spacing: 0.25, children: [imageChoice && option.imageAssetId && !failedImageValues.has(option.value) ? (_jsx(Box, { alt: option.label, component: "img", onError: () => setFailedImageValues((current) => new Set(current).add(option.value)), src: mediaUrl ? mediaUrl(option.imageAssetId) : "", sx: {
109
+ alignSelf: "center",
110
+ borderRadius: 1,
111
+ display: "block",
112
+ maxHeight: 220,
113
+ maxWidth: "100%",
114
+ objectFit: "contain",
115
+ } })) : null, _jsx(Typography, { children: option.label }), option.hint ? _jsx(Typography, { color: "text.secondary", sx: { whiteSpace: "pre-line" }, variant: "body2", children: renderRichText(option.hint, `option-hint-${option.value}`) }) : null, isOtherOption ? (_jsx(TextField, { disabled: !isSelected, fullWidth: true, inputProps: { "aria-label": option.label }, onClick: (event) => event.stopPropagation(), onChange: (event) => onOtherChange === null || onOtherChange === void 0 ? void 0 : onOtherChange(event.target.value), onKeyDown: (event) => event.stopPropagation(), size: "small", value: otherValue || "" })) : null] }) }, option.value));
116
+ const optionOverlay = renderOptionOverlay === null || renderOptionOverlay === void 0 ? void 0 : renderOptionOverlay(option);
117
+ if (!optionOverlay) {
118
+ return card;
119
+ }
120
+ return (_jsxs(Box, { sx: { position: "relative" }, children: [card, _jsx(Box, { sx: { position: "absolute", right: 4, top: 4 }, children: optionOverlay })] }, option.value));
121
+ });
122
+ const group = useResponsiveColumns ? (_jsx(Box, { role: "group", sx: {
123
+ display: "grid",
124
+ gap: 1.1,
125
+ gridTemplateColumns: {
126
+ xs: "1fr",
127
+ md: "repeat(2, minmax(0, 1fr))",
128
+ },
129
+ }, children: choiceCards })) : _jsx(Stack, { role: "group", spacing: 1.1, children: choiceCards });
130
+ return _jsxs(Stack, { spacing: 0.75, children: [group, hasMaximum ? _jsx(Typography, { color: "text.secondary", variant: "caption", children: t("surveyRenderer.MAX_SELECTIONS", { count: maximum }) }) : null] });
131
+ }
132
+ function StandardQuestionPrompt({ errorText, hint, label }) {
133
+ return _jsx(SurveyQuestionHeader, { errorText: errorText, hint: hint, label: label });
134
+ }
135
+ function buildLocalizedQuestionOptions(question, language, runtimeOptions = []) {
136
+ const localizeOption = (option, fallback = {}) => {
137
+ var _a;
138
+ return (Object.assign({ value: option.value, label: localizeSurveyValue(option.labels, language) || fallback.label, hint: localizeSurveyValue(option.hints, language) || fallback.hint, isOther: Boolean(option.is_other) }, (Number.isInteger((_a = option.media) === null || _a === void 0 ? void 0 : _a.image) && option.media.image > 0 ? { imageAssetId: option.media.image } : {})));
139
+ };
140
+ const fallbackOptions = (question.options || []).map((option) => localizeOption(option));
141
+ if (!Array.isArray(runtimeOptions) || runtimeOptions.length === 0) {
142
+ return fallbackOptions;
143
+ }
144
+ const questionOptionsByValue = new Map((question.options || []).map((option) => [String(option.value), option]));
145
+ return runtimeOptions.map((option) => {
146
+ const questionOption = questionOptionsByValue.get(String(option.value));
147
+ if (!questionOption) {
148
+ return option;
149
+ }
150
+ return localizeOption(questionOption, option);
151
+ });
152
+ }
153
+ function NoteField({ hideDuplicateIntroTitle, hint, label, question, surveyTitle }) {
154
+ const formatted = formatQuestionText(label);
155
+ const titledContent = parseTitledContent(label);
156
+ const hideNoteTitle = hideDuplicateIntroTitle && shouldHideNoteTitle((titledContent === null || titledContent === void 0 ? void 0 : titledContent.title) || formatted.text, surveyTitle);
157
+ if (titledContent) {
158
+ return (_jsxs(Stack, { spacing: 1, children: [!hideNoteTitle ? (_jsx(Typography, { fontWeight: 700, variant: "h5", children: renderRichText(titledContent.title, `${question.name}-title`) })) : null, titledContent.body ? (_jsx(Typography, { sx: { whiteSpace: "pre-line" }, children: renderRichText(titledContent.body, `${question.name}-body`) })) : null, hint ? _jsx(Typography, { color: "text.secondary", sx: { whiteSpace: "pre-line" }, children: renderRichText(hint, `${question.name}-hint`) }) : null] }));
159
+ }
160
+ return (_jsxs(Stack, { spacing: 1, children: [!hideNoteTitle ? (_jsx(Typography, { fontWeight: formatted.kind === "title" ? 700 : 500, variant: formatted.kind === "title" ? "h5" : "body1", children: renderRichText(formatted.text, `${question.name}-formatted`) })) : null, hint ? _jsx(Typography, { color: "text.secondary", sx: { whiteSpace: "pre-line" }, children: renderRichText(hint, `${question.name}-formatted-hint`) }) : null] }));
161
+ }
162
+ function MapSelectOneField({ errorText, hint, label, onChange, options, question, resolvedQuestionUiConfig, stageOptions, value }) {
163
+ return (_jsx(SurveyMapSelect, { errorText: errorText, hint: hint, label: label, onChange: (nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), options: options, uiConfig: resolvedQuestionUiConfig, value: value || "" }));
164
+ }
165
+ function SelectOneField({ answers, errorText, hint, label, neutral, onChange, options, question, renderOptionOverlay, stageOptions, value }) {
166
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 2.25 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(ChoiceCardGroup, { neutral: neutral, onChange: (nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), onOtherChange: (nextValue) => onChange(`${question.name}_other`, nextValue, Object.assign({ autosave: true }, stageOptions)), options: options, otherOptionValue: question.or_other ? "other" : undefined, otherValue: answers[`${question.name}_other`], renderOptionOverlay: renderOptionOverlay, value: value || "" })] }));
167
+ }
168
+ function SelectOneOtherTextField({ answers, onChange, options, question, stageOptions, value }) {
169
+ const selectedOption = options.find((option) => option.value === value);
170
+ const isOtherSelected = question.or_other && ((selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.isOther) || value === "other");
171
+ if (!isOtherSelected) {
172
+ return null;
173
+ }
174
+ return (_jsx(TextField, { fullWidth: true, inputProps: { "aria-label": selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.label }, onChange: (event) => onChange(`${question.name}_other`, event.target.value, Object.assign({ autosave: true }, stageOptions)), size: "small", value: answers[`${question.name}_other`] || "" }));
175
+ }
176
+ function DropdownSelectOneField({ answers, errorText, hint, label, onChange, options, question, stageOptions, value }) {
177
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 2.25 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(TextField, { SelectProps: { inputProps: { "aria-label": label || question.name } }, fullWidth: true, onChange: (event) => onChange(question.name, event.target.value, Object.assign({ autosave: true }, stageOptions)), select: true, value: value || "", children: options.map((option) => _jsx(MenuItem, { value: option.value, children: option.label }, option.value)) }), _jsx(SelectOneOtherTextField, { answers: answers, onChange: onChange, options: options, question: question, stageOptions: stageOptions, value: value })] }));
178
+ }
179
+ function AutocompleteSelectOneField({ answers, errorText, hint, label, onChange, options, question, stageOptions, value }) {
180
+ const selectedOption = options.find((option) => option.value === value) || null;
181
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 2.25 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(Autocomplete, { getOptionLabel: (option) => option.label || option.value, isOptionEqualToValue: (option, selected) => option.value === selected.value, onChange: (_event, nextOption) => onChange(question.name, (nextOption === null || nextOption === void 0 ? void 0 : nextOption.value) || "", Object.assign({ autosave: true }, stageOptions)), options: options, renderInput: (params) => _jsx(TextField, Object.assign({}, params, { inputProps: Object.assign(Object.assign({}, params.inputProps), { "aria-label": label || question.name }) })), value: selectedOption }), _jsx(SelectOneOtherTextField, { answers: answers, onChange: onChange, options: options, question: question, stageOptions: stageOptions, value: value })] }));
182
+ }
183
+ function LikertField({ errorText, hint, label, onChange, options, question, stageOptions, value }) {
184
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 2.25 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(RadioGroup, { "aria-label": label || question.name, name: question.name, onChange: (event) => onChange(question.name, event.target.value, Object.assign({ autosave: true }, stageOptions)), row: true, sx: { alignItems: "stretch", flexWrap: "wrap", gap: 0.75 }, value: value || "", children: options.map((option) => (_jsxs(Box, { sx: {
185
+ border: "1px solid",
186
+ borderColor: value === option.value ? "primary.main" : "divider",
187
+ borderRadius: 1.5,
188
+ flex: "1 1 145px",
189
+ minWidth: 0,
190
+ px: 0.5,
191
+ }, children: [_jsx(Radio, { value: option.value, sx: { alignItems: "flex-start", width: "100%" }, slotProps: { input: { "aria-label": option.label } } }), _jsx(Typography, { component: "span", sx: { display: "inline-block", pb: 1, pr: 1 }, variant: "body2", children: option.label }), option.hint ? _jsx(Typography, { color: "text.secondary", sx: { display: "block", pb: 1, px: 1 }, variant: "caption", children: renderRichText(option.hint, `likert-option-hint-${option.value}`) }) : null] }, option.value))) })] }));
192
+ }
193
+ function MapSelectMultipleField({ errorText, hint, label, onChange, options, question, resolvedQuestionUiConfig, stageOptions, value }) {
194
+ return (_jsx(SurveyGeoSelect, { errorText: errorText, hint: hint, label: label, maxSelections: (resolvedQuestionUiConfig === null || resolvedQuestionUiConfig === void 0 ? void 0 : resolvedQuestionUiConfig.maxSelections) || 3, multiple: true, onChange: (nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), options: options, uiConfig: resolvedQuestionUiConfig, value: Array.isArray(value) ? value : [] }));
195
+ }
196
+ function FilteredSelectMultipleField({ answers, errorText, hint, label, onChange, options, optionsByName, question, resolvedQuestionUiConfig, stageOptions, value }) {
197
+ return (_jsx(SurveyFilteredPlaceSelect, { allOptionsByName: optionsByName, answers: answers, errorText: errorText, hint: hint, label: label, onChange: (nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), options: options, uiConfig: resolvedQuestionUiConfig, value: Array.isArray(value) ? value : [] }));
198
+ }
199
+ function DiscoverySelectMultipleField({ errorText, hint, label, onChange, options, optionsByName, question, resolvedQuestionUiConfig, stageOptions, value }) {
200
+ return (_jsx(SurveyDiscoverySelect, { allOptionsByName: optionsByName, errorText: errorText, hint: hint, label: label, onChange: (nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), options: options, uiConfig: resolvedQuestionUiConfig, value: Array.isArray(value) ? value : [] }));
201
+ }
202
+ function SelectMultipleField({ answers, errorText, hint, label, neutral, onChange, options, question, renderOptionOverlay, stageOptions, value }) {
203
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 2.25 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(ChoiceCardGroup, { maxSelections: question.max_selections, multiple: true, neutral: neutral, onChange: (nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), onOtherChange: (nextValue) => onChange(`${question.name}_other`, nextValue, Object.assign({ autosave: true }, stageOptions)), options: options, otherOptionValue: question.or_other ? "other" : undefined, otherValue: answers[`${question.name}_other`], renderOptionOverlay: renderOptionOverlay, value: value })] }));
204
+ }
205
+ function ImageChoiceSelectOneField({ answers, errorText, hint, label, mediaUrl, neutral, onChange, options, question, renderOptionOverlay, stageOptions, value }) {
206
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 2.25 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(ChoiceCardGroup, { imageChoice: true, mediaUrl: mediaUrl, neutral: neutral, onChange: (nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), onOtherChange: (nextValue) => onChange(`${question.name}_other`, nextValue, Object.assign({ autosave: true }, stageOptions)), options: options, otherOptionValue: question.or_other ? "other" : undefined, otherValue: answers[`${question.name}_other`], renderOptionOverlay: renderOptionOverlay, value: value || "" })] }));
207
+ }
208
+ function ImageChoiceSelectMultipleField({ answers, errorText, hint, label, mediaUrl, neutral, onChange, options, question, renderOptionOverlay, stageOptions, value }) {
209
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 2.25 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(ChoiceCardGroup, { imageChoice: true, maxSelections: question.max_selections, mediaUrl: mediaUrl, multiple: true, neutral: neutral, onChange: (nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), onOtherChange: (nextValue) => onChange(`${question.name}_other`, nextValue, Object.assign({ autosave: true }, stageOptions)), options: options, otherOptionValue: question.or_other ? "other" : undefined, otherValue: answers[`${question.name}_other`], renderOptionOverlay: renderOptionOverlay, value: value })] }));
210
+ }
211
+ function RankField({ errorText, hint, label, onChange, options, question, stageOptions, value }) {
212
+ const { t } = useTranslation();
213
+ const rankMaximum = Number(question.rank_max);
214
+ const hasRankMaximum = Number.isInteger(rankMaximum) && rankMaximum > 0;
215
+ const optionValues = new Set(options.map((option) => option.value));
216
+ const validSelectedValues = Array.isArray(value)
217
+ ? value.filter((optionValue, index, values) => optionValues.has(optionValue) && values.indexOf(optionValue) === index)
218
+ : [];
219
+ // If rank_max was lowered after an answer was already stored under a higher (or absent) cap,
220
+ // treat only the first rankMaximum entries as still included -- the rest become re-includable
221
+ // rather than silently staying over-cap and reorderable.
222
+ const selectedValues = hasRankMaximum ? validSelectedValues.slice(0, rankMaximum) : validSelectedValues;
223
+ const rankedValues = hasRankMaximum
224
+ ? selectedValues
225
+ : [...selectedValues, ...options.map((option) => option.value).filter((optionValue) => !selectedValues.includes(optionValue))];
226
+ const currentRankValues = hasRankMaximum ? selectedValues : rankedValues;
227
+ const rankedOptions = rankedValues
228
+ .map((optionValue) => options.find((option) => option.value === optionValue))
229
+ .filter(Boolean);
230
+ const unrankedOptions = hasRankMaximum
231
+ ? options.filter((option) => !selectedValues.includes(option.value))
232
+ : [];
233
+ const displayedOptions = [...rankedOptions, ...unrankedOptions];
234
+ const saveRank = (nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions));
235
+ const moveOption = (optionValue, direction) => {
236
+ const currentIndex = currentRankValues.indexOf(optionValue);
237
+ const nextIndex = currentIndex + direction;
238
+ if (currentIndex < 0 || nextIndex < 0 || nextIndex >= currentRankValues.length) {
239
+ return;
240
+ }
241
+ const nextValues = [...currentRankValues];
242
+ [nextValues[currentIndex], nextValues[nextIndex]] = [nextValues[nextIndex], nextValues[currentIndex]];
243
+ saveRank(nextValues);
244
+ };
245
+ const toggleOption = (optionValue) => {
246
+ if (selectedValues.includes(optionValue)) {
247
+ saveRank(selectedValues.filter((selectedValue) => selectedValue !== optionValue));
248
+ }
249
+ else if (selectedValues.length < rankMaximum) {
250
+ saveRank([...selectedValues, optionValue]);
251
+ }
252
+ };
253
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 2.25 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(Box, { component: "ol", sx: { display: "grid", gap: 0.75, listStylePosition: "inside", m: 0, p: 0 }, children: displayedOptions.map((option) => {
254
+ const rankIndex = currentRankValues.indexOf(option.value);
255
+ const isIncluded = rankIndex >= 0;
256
+ const cannotInclude = hasRankMaximum && !isIncluded && selectedValues.length >= rankMaximum;
257
+ return (_jsxs(Box, { component: "li", sx: { alignItems: "center", border: "1px solid", borderColor: "divider", borderRadius: 1.5, display: "flex", gap: 0.5, pl: 0.75, pr: 0.5, py: 0.5 }, children: [hasRankMaximum ? (_jsx(Checkbox, { checked: isIncluded, disabled: cannotInclude, inputProps: {
258
+ "aria-label": t(isIncluded ? "surveyRenderer.RANK_EXCLUDE" : "surveyRenderer.RANK_INCLUDE", {
259
+ defaultValue: isIncluded ? "Remove {{option}} from ranking" : "Include {{option}} in ranking",
260
+ option: option.label,
261
+ }),
262
+ }, onChange: () => toggleOption(option.value) })) : null, _jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsx(Typography, { children: option.label }), option.hint ? _jsx(Typography, { color: "text.secondary", variant: "caption", children: renderRichText(option.hint, `rank-option-hint-${option.value}`) }) : null] }), _jsx(IconButton, { "aria-label": t("surveyRenderer.RANK_MOVE_UP", { defaultValue: "Move {{option}} up", option: option.label }), disabled: !isIncluded || rankIndex === 0, onClick: () => moveOption(option.value, -1), size: "small", children: _jsx(KeyboardArrowUpRoundedIcon, {}) }), _jsx(IconButton, { "aria-label": t("surveyRenderer.RANK_MOVE_DOWN", { defaultValue: "Move {{option}} down", option: option.label }), disabled: !isIncluded || rankIndex === currentRankValues.length - 1, onClick: () => moveOption(option.value, 1), size: "small", children: _jsx(KeyboardArrowDownRoundedIcon, {}) })] }, option.value));
263
+ }) })] }));
264
+ }
265
+ function RangeField({ errorText, hint, label, onChange, question, stageOptions, value }) {
266
+ const { max, min, step } = getRangeParameters(question.parameters);
267
+ const numericValue = Number(value);
268
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 1.75 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(Slider, { "aria-label": label || question.name, max: max, min: min, onChange: (_, nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), step: step, value: Number.isFinite(numericValue) ? numericValue : min })] }));
269
+ }
270
+ function RatingField({ errorText, hint, label, onChange, question, stageOptions, value }) {
271
+ const { max, min } = getRangeParameters(question.parameters);
272
+ const numericValue = Number(value);
273
+ const ratingValue = Number.isFinite(numericValue) && numericValue >= Math.max(min, 1) && numericValue <= max
274
+ ? numericValue
275
+ : null;
276
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 1.75 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(Rating, { getLabelText: (rating) => String(rating), max: max, name: question.name, onChange: (event, nextValue) => {
277
+ const selectedRating = Number.isFinite(nextValue) ? nextValue : Number(event.target.value);
278
+ onChange(question.name, Number.isFinite(selectedRating) ? selectedRating : "", Object.assign({ autosave: true }, stageOptions));
279
+ }, value: ratingValue })] }));
280
+ }
281
+ function NpsField({ errorText, hint, label, onChange, question, stageOptions, value }) {
282
+ const { t } = useTranslation();
283
+ const { max, min, step } = getRangeParameters(question.parameters);
284
+ const numericValue = Number(value);
285
+ const values = [];
286
+ for (let current = min; current <= max; current += step) {
287
+ values.push(current);
288
+ }
289
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 1.75 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(RadioGroup, { "aria-label": label || question.name, name: question.name, onChange: (event) => onChange(question.name, Number(event.target.value), Object.assign({ autosave: true }, stageOptions)), row: true, sx: { display: "grid", gap: 0.65, gridTemplateColumns: "repeat(auto-fit, minmax(38px, 1fr))" }, value: Number.isFinite(numericValue) ? String(numericValue) : "", children: values.map((optionValue) => (_jsxs(Box, { sx: {
290
+ alignItems: "center",
291
+ border: "1px solid",
292
+ borderColor: numericValue === optionValue ? "primary.main" : "divider",
293
+ borderRadius: 1.5,
294
+ display: "flex",
295
+ flexDirection: "column",
296
+ py: 0.5,
297
+ }, children: [_jsx(Radio, { slotProps: { input: { "aria-label": String(optionValue) } }, value: String(optionValue) }), _jsx(Typography, { variant: "caption", children: optionValue })] }, optionValue))) }), _jsxs(Stack, { direction: "row", justifyContent: "space-between", spacing: 2, children: [_jsx(Typography, { color: "text.secondary", variant: "caption", children: t("surveyRenderer.NPS_NOT_LIKELY", { defaultValue: "Not at all likely" }) }), _jsx(Typography, { color: "text.secondary", sx: { textAlign: "right" }, variant: "caption", children: t("surveyRenderer.NPS_EXTREMELY_LIKELY", { defaultValue: "Extremely likely" }) })] })] }));
298
+ }
299
+ function SemanticDifferentialField({ errorText, hint, label, language, onChange, question, stageOptions, value }) {
300
+ const { max, min, step } = getRangeParameters(question.parameters);
301
+ const numericValue = Number(value);
302
+ const leftLabel = localizeSurveyValue(question.left_labels, language);
303
+ const rightLabel = localizeSurveyValue(question.right_labels, language);
304
+ const ariaLabel = [label || question.name, leftLabel, rightLabel].filter(Boolean).join(": ");
305
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 1.75 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsxs(Stack, { alignItems: "center", direction: { xs: "column", sm: "row" }, spacing: 1.25, children: [_jsx(Typography, { component: "span", sx: { flex: "0 1 170px", textAlign: { sm: "right" }, width: { xs: "100%", sm: "auto" } }, variant: "body2", children: leftLabel }), _jsx(Slider, { "aria-label": ariaLabel, max: max, min: min, onChange: (_, nextValue) => onChange(question.name, nextValue, Object.assign({ autosave: true }, stageOptions)), step: step, sx: { flex: 1, minWidth: 160 }, value: Number.isFinite(numericValue) ? numericValue : min }), _jsx(Typography, { component: "span", sx: { flex: "0 1 170px", width: { xs: "100%", sm: "auto" } }, variant: "body2", children: rightLabel })] })] }));
306
+ }
307
+ function pathsEqual(left, right) {
308
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
309
+ return false;
310
+ }
311
+ return left.every((value, index) => value === right[index]);
312
+ }
313
+ function MatrixTableListGroup({ answers, contextAnswers, errors, getQuestionDragProps, groupsByName, language, onChange, optionsByName, questionUiConfig, questions, renderQuestionToolbar, section, serverVisibleQuestionNames, }) {
314
+ const matrixQuestions = (questions || []).filter((question) => question.type === "select_one" &&
315
+ pathsEqual(question.group_path, section === null || section === void 0 ? void 0 : section.path) &&
316
+ isQuestionVisible(question, answers, serverVisibleQuestionNames, questionUiConfig, groupsByName));
317
+ const firstQuestion = matrixQuestions[0];
318
+ const options = firstQuestion
319
+ ? buildLocalizedQuestionOptions(firstQuestion, language, optionsByName[firstQuestion.name])
320
+ : [];
321
+ if (!firstQuestion) {
322
+ return null;
323
+ }
324
+ return (_jsx(TableContainer, { component: Paper, variant: "outlined", children: _jsxs(Table, { "aria-label": localizeSurveyValue(section === null || section === void 0 ? void 0 : section.labels, language) || (section === null || section === void 0 ? void 0 : section.name) || "matrix", size: "small", sx: { minWidth: 560 }, children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { component: "th", scope: "col" }), options.map((option) => _jsx(TableCell, { align: "center", component: "th", scope: "col", children: option.label }, option.value))] }) }), _jsx(TableBody, { children: matrixQuestions.map((question) => {
325
+ const stageGroupKey = getQuestionStagedGroupKey(question, questionUiConfig, groupsByName);
326
+ const stageOptions = stageGroupKey ? { stagedGroup: stageGroupKey } : {};
327
+ const questionLabel = interpolateSurveyText(localizeSurveyValue(question.labels, language), contextAnswers || answers);
328
+ const questionToolbar = renderQuestionToolbar === null || renderQuestionToolbar === void 0 ? void 0 : renderQuestionToolbar(question);
329
+ const questionDragProps = (getQuestionDragProps === null || getQuestionDragProps === void 0 ? void 0 : getQuestionDragProps(question)) || {};
330
+ return (_jsxs(TableRow, Object.assign({}, questionDragProps, { children: [_jsx(TableCell, { component: "th", "data-question-name": question.name, scope: "row", children: _jsxs(Stack, { spacing: 0.5, children: [questionToolbar, _jsx(Typography, { variant: "body2", children: questionLabel }), errors[question.name] ? _jsx(Typography, { color: "error", role: "alert", variant: "caption", children: errors[question.name] }) : null] }) }), options.map((option) => (_jsx(TableCell, { align: "center", children: _jsx(Radio, { checked: answers[question.name] === option.value, name: question.name, onChange: () => onChange(question.name, option.value, Object.assign({ autosave: true }, stageOptions)), slotProps: { input: { "aria-label": `${questionLabel}: ${option.label}` } }, value: option.value }) }, option.value)))] }), question.name));
331
+ }) })] }) }));
332
+ }
333
+ function DefaultTextField({ errorText, hint, label, neutral, onBlurSave, onChange, question, stageOptions, value }) {
334
+ return (_jsxs(Stack, { spacing: 1, sx: { pb: 1.75 }, children: [_jsx(StandardQuestionPrompt, { errorText: errorText, hint: hint, label: label }), _jsx(TextField, { fullWidth: true, multiline: question.type === "text" && question.appearance === "multiline", onBlur: (event) => onBlurSave(question.name, event.target.value, stageOptions), onChange: (event) => onChange(question.name, event.target.value, stageOptions), sx: {
335
+ ".MuiOutlinedInput-root": {
336
+ backgroundColor: neutral ? "background.paper" : "rgba(255, 243, 249, 0.96)",
337
+ },
338
+ }, type: mapInputType(question.input_type), value: value || "" })] }));
339
+ }
340
+ const QUESTION_RENDERER_REGISTRY = {
341
+ note: NoteField,
342
+ "select_one:map-select": MapSelectOneField,
343
+ "select_one:likert": LikertField,
344
+ "select_one:minimal": DropdownSelectOneField,
345
+ "select_one:autocomplete": AutocompleteSelectOneField,
346
+ "select_one:image-choice": ImageChoiceSelectOneField,
347
+ select_one: SelectOneField,
348
+ "select_multiple:map-select": MapSelectMultipleField,
349
+ "select_multiple:filtered-select": FilteredSelectMultipleField,
350
+ "select_multiple:discovery-select": DiscoverySelectMultipleField,
351
+ "select_multiple:image-choice": ImageChoiceSelectMultipleField,
352
+ select_multiple: SelectMultipleField,
353
+ rank: RankField,
354
+ "range:rating": RatingField,
355
+ "range:nps": NpsField,
356
+ "range:semantic-differential": SemanticDifferentialField,
357
+ range: RangeField,
358
+ default: DefaultTextField,
359
+ };
360
+ function resolveQuestionRenderer(question, resolvedQuestionUiConfig) {
361
+ const widget = resolvedQuestionUiConfig === null || resolvedQuestionUiConfig === void 0 ? void 0 : resolvedQuestionUiConfig.widget;
362
+ if (question.type === "select_one") {
363
+ if (widget === "map-select") {
364
+ return QUESTION_RENDERER_REGISTRY["select_one:map-select"];
365
+ }
366
+ if (question.appearance === "likert") {
367
+ return QUESTION_RENDERER_REGISTRY["select_one:likert"];
368
+ }
369
+ if (question.appearance === "minimal") {
370
+ return QUESTION_RENDERER_REGISTRY["select_one:minimal"];
371
+ }
372
+ if (question.appearance === "autocomplete") {
373
+ return QUESTION_RENDERER_REGISTRY["select_one:autocomplete"];
374
+ }
375
+ if ((question.options || []).some((option) => { var _a; return Number.isInteger((_a = option.media) === null || _a === void 0 ? void 0 : _a.image) && option.media.image > 0; })) {
376
+ return QUESTION_RENDERER_REGISTRY["select_one:image-choice"];
377
+ }
378
+ return QUESTION_RENDERER_REGISTRY.select_one;
379
+ }
380
+ if (question.type === "select_multiple") {
381
+ if (widget === "map-select") {
382
+ return QUESTION_RENDERER_REGISTRY["select_multiple:map-select"];
383
+ }
384
+ if (widget === "filtered-select") {
385
+ return QUESTION_RENDERER_REGISTRY["select_multiple:filtered-select"];
386
+ }
387
+ if (widget === "discovery-select") {
388
+ return QUESTION_RENDERER_REGISTRY["select_multiple:discovery-select"];
389
+ }
390
+ if ((question.options || []).some((option) => { var _a; return Number.isInteger((_a = option.media) === null || _a === void 0 ? void 0 : _a.image) && option.media.image > 0; })) {
391
+ return QUESTION_RENDERER_REGISTRY["select_multiple:image-choice"];
392
+ }
393
+ return QUESTION_RENDERER_REGISTRY.select_multiple;
394
+ }
395
+ if (question.type === "range") {
396
+ if (question.appearance === "rating") {
397
+ return QUESTION_RENDERER_REGISTRY["range:rating"];
398
+ }
399
+ if (question.appearance === "nps") {
400
+ return QUESTION_RENDERER_REGISTRY["range:nps"];
401
+ }
402
+ if (question.appearance === "semantic-differential") {
403
+ return QUESTION_RENDERER_REGISTRY["range:semantic-differential"];
404
+ }
405
+ return QUESTION_RENDERER_REGISTRY.range;
406
+ }
407
+ return QUESTION_RENDERER_REGISTRY[question.type] || QUESTION_RENDERER_REGISTRY.default;
408
+ }
409
+ const GROUP_RENDERER_REGISTRY = {
410
+ "table-list": MatrixTableListGroup,
411
+ };
412
+ function resolveGroupRenderer(section) {
413
+ return GROUP_RENDERER_REGISTRY[section === null || section === void 0 ? void 0 : section.appearance] || null;
414
+ }
415
+ function QuestionField({ answers, contextAnswers, errors, groupsByName, hideDuplicateIntroTitle, language, onBlurSave, onChange, neutral = false, optionsByName, question, questionUiConfig, mediaUrl, renderOptionOverlay, surveyTitle, }) {
416
+ if (question.other_of) {
417
+ return null;
418
+ }
419
+ const value = answers[question.name];
420
+ const interpolationAnswers = contextAnswers || answers;
421
+ const label = interpolateSurveyText(localizeSurveyValue(question.labels, language), interpolationAnswers);
422
+ const hint = interpolateSurveyText(localizeSurveyValue(question.hints, language), interpolationAnswers);
423
+ const errorText = errors[question.name];
424
+ const resolvedQuestionUiConfig = resolveQuestionUiConfig(question, questionUiConfig);
425
+ const stagedGroupKey = getQuestionStagedGroupKey(question, questionUiConfig, groupsByName);
426
+ const stageOptions = stagedGroupKey ? { stagedGroup: stagedGroupKey } : {};
427
+ const options = buildLocalizedQuestionOptions(question, language, optionsByName[question.name]);
428
+ if (question.type === "calculate" || question.is_calculated) {
429
+ return null;
430
+ }
431
+ const Renderer = resolveQuestionRenderer(question, resolvedQuestionUiConfig);
432
+ return (_jsx(Renderer, { answers: answers, errorText: errorText, hideDuplicateIntroTitle: hideDuplicateIntroTitle, hint: hint, label: label, mediaUrl: mediaUrl, neutral: neutral, onBlurSave: onBlurSave, onChange: onChange, options: options, optionsByName: optionsByName, question: question, resolvedQuestionUiConfig: resolvedQuestionUiConfig, renderOptionOverlay: renderOptionOverlay ? (option) => renderOptionOverlay(question, option) : undefined, stageOptions: stageOptions, surveyTitle: surveyTitle, value: value }));
433
+ }
434
+ function getRepeatError(errors, repeatName, repeatIndex, questionName) {
435
+ return errors[`${repeatName}.${repeatIndex}.${questionName}`] || "";
436
+ }
437
+ function RepeatSection({ answers, entryIndex = null, errors, language, mediaUrl, onBlurSave, onChange, onRepeatAdd, onRepeatRemove, neutral = false, optionsByName, questionUiConfig, renderOptionOverlay, repeatEntries, repeatEvaluation, section, }) {
438
+ const { t } = useTranslation();
439
+ const repeatName = section.name;
440
+ const allEntries = Array.isArray(repeatEntries) ? repeatEntries : [];
441
+ const entries = entryIndex === null ? allEntries : allEntries.slice(entryIndex, entryIndex + 1);
442
+ const evaluationEntries = (repeatEvaluation === null || repeatEvaluation === void 0 ? void 0 : repeatEvaluation.entries) || [];
443
+ const sourceQuestion = detectRepeatSourceQuestion(section);
444
+ const titleQuestion = (section.questions || []).find((question) => isTitleNote(question, language));
445
+ return (_jsxs(Stack, { spacing: 2.5, children: [entries.map((entryAnswers, visibleIndex) => {
446
+ var _a, _b;
447
+ const repeatIndex = entryIndex === null ? visibleIndex : entryIndex + visibleIndex;
448
+ const entryVisibleQuestions = ((_a = evaluationEntries[repeatIndex]) === null || _a === void 0 ? void 0 : _a.visible_questions) || [];
449
+ const entryOptions = ((_b = evaluationEntries[repeatIndex]) === null || _b === void 0 ? void 0 : _b.options) || {};
450
+ const entryContext = Object.assign(Object.assign({}, answers), entryAnswers);
451
+ const repeatLabel = entryContext.place_label || entryContext.placeLabel || "";
452
+ const entryTitle = titleQuestion
453
+ ? formatQuestionText(interpolateSurveyText(localizeSurveyValue(titleQuestion.labels, language), entryContext)).text
454
+ : repeatLabel;
455
+ const repeatQuestions = entryVisibleQuestions.length > 0
456
+ ? (section.questions || []).filter((question) => entryVisibleQuestions.includes(question.name))
457
+ : section.questions || [];
458
+ const filteredRepeatQuestions = repeatQuestions.filter((question) => !(question.other_of || (titleQuestion && question.name === titleQuestion.name)));
459
+ return (_jsx(Paper, { sx: {
460
+ backgroundColor: neutral ? "background.paper" : "rgba(255, 243, 249, 0.96)",
461
+ border: "1px solid",
462
+ borderColor: "rgba(17, 17, 17, 0.08)",
463
+ boxShadow: "none",
464
+ p: 2,
465
+ }, children: _jsxs(Stack, { spacing: 2, children: [_jsxs(Stack, { alignItems: { xs: "stretch", sm: "center" }, direction: { xs: "column", sm: "row" }, justifyContent: "space-between", spacing: 1, children: [entryTitle ? _jsx(Typography, { sx: { whiteSpace: "pre-line" }, variant: "subtitle1", children: entryTitle }) : _jsx(Box, {}), !sourceQuestion ? (_jsx(Button, { color: "error", onClick: () => onRepeatRemove(repeatName, repeatIndex), variant: "outlined", children: t("surveyRenderer.REPEAT_REMOVE", "Remove") })) : null] }), _jsx(Divider, {}), filteredRepeatQuestions.map((question) => (_jsx("div", { "data-question-name": question.name, children: _jsx(QuestionField, { answers: entryAnswers, contextAnswers: entryContext, errors: { [question.name]: getRepeatError(errors, repeatName, repeatIndex, question.name) }, groupsByName: {}, language: language, mediaUrl: mediaUrl, onBlurSave: (questionName, value) => onBlurSave(questionName, value, { repeatName, repeatIndex }), onChange: (questionName, value, options = {}) => onChange(questionName, value, Object.assign(Object.assign({}, options), { repeatName, repeatIndex })), neutral: neutral, optionsByName: entryOptions, question: question, questionUiConfig: questionUiConfig, renderOptionOverlay: renderOptionOverlay }) }, `${repeatName}-${repeatIndex}-${question.name}`)))] }) }, `${repeatName}-${repeatIndex}`));
466
+ }), !sourceQuestion ? (_jsx(Button, { onClick: () => onRepeatAdd(repeatName), variant: "outlined", children: t("surveyRenderer.REPEAT_ADD", "Add entry") })) : null] }));
467
+ }
468
+ function mapInputType(inputType) {
469
+ if (["integer", "decimal"].includes(inputType)) {
470
+ return "number";
471
+ }
472
+ if (inputType === "date") {
473
+ return "date";
474
+ }
475
+ if (inputType === "time") {
476
+ return "time";
477
+ }
478
+ if (inputType === "dateTime") {
479
+ return "datetime-local";
480
+ }
481
+ if (["email", "url", "tel"].includes(inputType)) {
482
+ return inputType;
483
+ }
484
+ return "text";
485
+ }
486
+ function getRangeParameters(parameters) {
487
+ const values = parameters && typeof parameters === "object" ? parameters : {};
488
+ let min = Number(values.start);
489
+ let max = Number(values.end);
490
+ let step = Number(values.step);
491
+ if (!Number.isFinite(min)) {
492
+ min = 0;
493
+ }
494
+ if (!Number.isFinite(max) || max <= min) {
495
+ min = 0;
496
+ max = 10;
497
+ }
498
+ if (!Number.isFinite(step) || step <= 0) {
499
+ step = 1;
500
+ }
501
+ return { min, max, step };
502
+ }
503
+ function getSectionTitle(section, language, fallbackIndex, t) {
504
+ const directLabel = localizeSurveyValue(section === null || section === void 0 ? void 0 : section.labels, language);
505
+ if (directLabel) {
506
+ return directLabel;
507
+ }
508
+ return "";
509
+ }
510
+ function getPageNavigationLabel(pageItem, index, language, answers, t) {
511
+ var _a, _b;
512
+ if (!pageItem) {
513
+ return t("surveyRenderer.SECTION_DEFAULT", { index: index + 1 });
514
+ }
515
+ if (pageItem.pageType === "repeat") {
516
+ const repeatEntry = Array.isArray(answers === null || answers === void 0 ? void 0 : answers[(_a = pageItem.section) === null || _a === void 0 ? void 0 : _a.name])
517
+ ? ((_b = answers[pageItem.section.name]) === null || _b === void 0 ? void 0 : _b[pageItem.repeatIndex]) || {}
518
+ : {};
519
+ const repeatLabel = (repeatEntry === null || repeatEntry === void 0 ? void 0 : repeatEntry.place_label) || (repeatEntry === null || repeatEntry === void 0 ? void 0 : repeatEntry.placeLabel) || "";
520
+ const baseTitle = pageItem.title || t("surveyRenderer.SECTION_DEFAULT", { index: index + 1 });
521
+ return repeatLabel || baseTitle;
522
+ }
523
+ return pageItem.title || t("surveyRenderer.SECTION_DEFAULT", { index: index + 1 });
524
+ }
525
+ function getPageErrorKeys(pageItem) {
526
+ var _a;
527
+ if (!pageItem) {
528
+ return [];
529
+ }
530
+ if (pageItem.pageType === "section") {
531
+ return (pageItem.questions || []).map((question) => question.name);
532
+ }
533
+ if (pageItem.pageType === "repeat") {
534
+ return (((_a = pageItem.section) === null || _a === void 0 ? void 0 : _a.questions) || []).map((question) => `${pageItem.section.name}.${pageItem.repeatIndex}.${question.name}`);
535
+ }
536
+ return [];
537
+ }
538
+ function buildSubmitErrorSummary({ answers, errors, language, pageItems, t }) {
539
+ if (!errors || Object.keys(errors).length === 0) {
540
+ return [];
541
+ }
542
+ return pageItems
543
+ .map((pageItem, index) => {
544
+ const errorKeys = getPageErrorKeys(pageItem);
545
+ const errorCount = errorKeys.filter((errorKey) => Boolean(errors[errorKey])).length;
546
+ if (errorCount === 0) {
547
+ return null;
548
+ }
549
+ return {
550
+ errorCount,
551
+ index,
552
+ key: pageItem.key,
553
+ label: getPageNavigationLabel(pageItem, index, language, answers, t),
554
+ };
555
+ })
556
+ .filter(Boolean);
557
+ }
558
+ function detectRepeatSourceQuestion(section) {
559
+ const sourceQuestion = (section.questions || []).find((question) => /selected_at\(\s*[A-Za-z0-9_]+\s*,\s*position\(\)\s*-\s*1\s*\)/.test(question.calculation || ""));
560
+ if (!sourceQuestion) {
561
+ return null;
562
+ }
563
+ const match = (sourceQuestion.calculation || "").match(/selected_at\(\s*([A-Za-z0-9_]+)\s*,\s*position\(\)\s*-\s*1\s*\)/);
564
+ return match ? match[1] : null;
565
+ }
566
+ function getLeafGroups(groups, repeatGroupNames) {
567
+ const nonRepeatGroups = (groups || []).filter((group) => !repeatGroupNames.has(group.name));
568
+ const parentGroupNames = new Set();
569
+ nonRepeatGroups.forEach((group) => {
570
+ const path = Array.isArray(group.path || group.group_path) ? group.path || group.group_path : [];
571
+ path.forEach((entry) => {
572
+ if (entry !== group.name) {
573
+ parentGroupNames.add(entry);
574
+ }
575
+ });
576
+ });
577
+ return nonRepeatGroups
578
+ .filter((group) => !parentGroupNames.has(group.name))
579
+ .sort((left, right) => (left.order || 0) - (right.order || 0));
580
+ }
581
+ function buildOrderedPageItems({ answers, definition, allQuestions, evaluation, introQuestions, language, orderedLeafGroups, repeatGroupNames, surveyTitle, t, }) {
582
+ const pageItems = [];
583
+ const leafGroupNames = new Set(orderedLeafGroups.map((group) => group.name));
584
+ if (introQuestions.length > 0) {
585
+ pageItems.push({
586
+ key: "intro",
587
+ pageType: "section",
588
+ questions: introQuestions,
589
+ section: null,
590
+ title: surveyTitle,
591
+ });
592
+ }
593
+ (definition.groups || [])
594
+ .slice()
595
+ .sort((left, right) => (left.order || 0) - (right.order || 0))
596
+ .forEach((group, index) => {
597
+ var _a, _b;
598
+ if (repeatGroupNames.has(group.name)) {
599
+ const repeatQuestions = allQuestions.filter((question) => question.repeat === group.name);
600
+ const repeatEntries = Array.isArray(answers === null || answers === void 0 ? void 0 : answers[group.name]) ? answers[group.name] : [];
601
+ const repeatEvaluation = (_a = evaluation === null || evaluation === void 0 ? void 0 : evaluation.repeats) === null || _a === void 0 ? void 0 : _a[group.name];
602
+ const hasSourceQuestion = Boolean(detectRepeatSourceQuestion({ questions: repeatQuestions }));
603
+ const hasVisibleRepeatEntries = ((repeatEvaluation === null || repeatEvaluation === void 0 ? void 0 : repeatEvaluation.entries) || []).length > 0 || repeatEntries.length > 0;
604
+ if (hasSourceQuestion && !hasVisibleRepeatEntries) {
605
+ return;
606
+ }
607
+ const entryCount = Math.max(repeatEntries.length, ((_b = repeatEvaluation === null || repeatEvaluation === void 0 ? void 0 : repeatEvaluation.entries) === null || _b === void 0 ? void 0 : _b.length) || 0);
608
+ for (let repeatIndex = 0; repeatIndex < entryCount; repeatIndex += 1) {
609
+ pageItems.push({
610
+ key: `repeat-${group.name}-${repeatIndex}`,
611
+ pageType: "repeat",
612
+ repeatIndex,
613
+ section: Object.assign(Object.assign({}, group), { questions: repeatQuestions }),
614
+ title: getSectionTitle(group, language, pageItems.length + index, t),
615
+ });
616
+ }
617
+ if (!hasSourceQuestion && entryCount === 0) {
618
+ pageItems.push({
619
+ key: `repeat-${group.name}`,
620
+ pageType: "repeat",
621
+ repeatIndex: null,
622
+ section: Object.assign(Object.assign({}, group), { questions: repeatQuestions }),
623
+ title: getSectionTitle(group, language, pageItems.length + index, t),
624
+ });
625
+ }
626
+ return;
627
+ }
628
+ if (!leafGroupNames.has(group.name)) {
629
+ return;
630
+ }
631
+ const groupedQuestions = definition.questions.filter((question) => question.group === group.name);
632
+ if (groupedQuestions.length === 0) {
633
+ return;
634
+ }
635
+ pageItems.push({
636
+ key: group.name,
637
+ pageType: "section",
638
+ questions: groupedQuestions,
639
+ section: group,
640
+ title: getSectionTitle(group, language, pageItems.length + index, t),
641
+ });
642
+ });
643
+ const assignedQuestionNames = new Set(pageItems
644
+ .filter((item) => item.pageType === "section")
645
+ .flatMap((item) => (item.questions || []).map((question) => question.name)));
646
+ const remainingQuestions = definition.questions.filter((question) => {
647
+ if (question.repeat || assignedQuestionNames.has(question.name)) {
648
+ return false;
649
+ }
650
+ return (question.group_path || []).length > 0;
651
+ });
652
+ if (remainingQuestions.length > 0) {
653
+ pageItems.push({
654
+ key: "other",
655
+ pageType: "section",
656
+ questions: remainingQuestions,
657
+ section: null,
658
+ title: getSectionTitle(null, language, pageItems.length, t),
659
+ });
660
+ }
661
+ return pageItems;
662
+ }
663
+ function pageHasDiscoverySelect(pageItem, questionUiConfig) {
664
+ if ((pageItem === null || pageItem === void 0 ? void 0 : pageItem.pageType) !== "section") {
665
+ return false;
666
+ }
667
+ return (pageItem.questions || []).some((question) => { var _a; return ((_a = resolveQuestionUiConfig(question, questionUiConfig)) === null || _a === void 0 ? void 0 : _a.widget) === "discovery-select"; });
668
+ }
669
+ const SurveyRenderer = ({ answers, callToAction, description, currentPageKey = "", errors, enforceValidation = true, hideDuplicateIntroTitle = false, hideSurveyTitle = false, language, loading, mediaUrl, onAttemptNextPage, onBlurSave, onChange, onPageChange, onSubmit, onRepeatAdd, onRepeatRemove, optionsByName, questionUiConfig, neutral = false, renderGroupToolbar, renderOptionOverlay, renderQuestionToolbar, getQuestionDragProps, saving, summaryErrors = {}, submitAttempted = false, submitLabel, submitting, survey, }) => {
670
+ var _a;
671
+ const { t } = useTranslation();
672
+ const hasDefinition = Boolean(survey === null || survey === void 0 ? void 0 : survey.definition);
673
+ const definition = (survey === null || survey === void 0 ? void 0 : survey.definition) || { questions: [], groups: [], repeats: [] };
674
+ const evaluation = (survey === null || survey === void 0 ? void 0 : survey.evaluation) || { visible_questions: [], repeats: {} };
675
+ const groupsByName = React.useMemo(() => getGroupsByName(definition), [definition]);
676
+ const serverVisibleQuestionNames = React.useMemo(() => new Set(evaluation.visible_questions || []), [evaluation.visible_questions]);
677
+ const stagedQuestionNames = React.useMemo(() => new Set(getStagedQuestionNames(definition, questionUiConfig, groupsByName)), [definition, groupsByName, questionUiConfig]);
678
+ const visibleQuestions = React.useMemo(() => definition.questions.filter((question) => serverVisibleQuestionNames.has(question.name) || stagedQuestionNames.has(question.name)), [definition, serverVisibleQuestionNames, stagedQuestionNames]);
679
+ const repeatGroupNames = React.useMemo(() => new Set((definition.repeats || []).map((group) => group.name)), [definition]);
680
+ const orderedGroups = React.useMemo(() => getLeafGroups(definition.groups || [], repeatGroupNames), [definition, repeatGroupNames]);
681
+ const introQuestions = React.useMemo(() => visibleQuestions.filter((question) => (question.group_path || []).length === 0), [visibleQuestions]);
682
+ const pageItems = React.useMemo(() => buildOrderedPageItems({
683
+ answers,
684
+ definition: Object.assign(Object.assign({}, definition), { questions: visibleQuestions }),
685
+ allQuestions: definition.questions || [],
686
+ evaluation,
687
+ introQuestions,
688
+ language,
689
+ orderedLeafGroups: orderedGroups,
690
+ repeatGroupNames,
691
+ surveyTitle: (survey === null || survey === void 0 ? void 0 : survey.title) || "",
692
+ t,
693
+ }), [
694
+ answers,
695
+ definition,
696
+ evaluation,
697
+ introQuestions,
698
+ language,
699
+ orderedGroups,
700
+ repeatGroupNames,
701
+ survey === null || survey === void 0 ? void 0 : survey.title,
702
+ t,
703
+ visibleQuestions,
704
+ ]);
705
+ const [pageIndex, setPageIndex] = React.useState(0);
706
+ const [maxAccessiblePageIndex, setMaxAccessiblePageIndex] = React.useState(0);
707
+ const [pendingAdvanceFromPageKey, setPendingAdvanceFromPageKey] = React.useState("");
708
+ const touchStartRef = React.useRef(null);
709
+ const onPageChangeRef = React.useRef(onPageChange);
710
+ const lastRestoredPageKeyRef = React.useRef("");
711
+ const pageTopRef = React.useRef(null);
712
+ const previousPageIndexRef = React.useRef(0);
713
+ React.useEffect(() => {
714
+ onPageChangeRef.current = onPageChange;
715
+ }, [onPageChange]);
716
+ const navigateToIndex = React.useCallback((nextIndex) => {
717
+ const boundedIndex = Math.max(0, Math.min(nextIndex, Math.max(pageItems.length - 1, 0)));
718
+ const nextPage = pageItems[boundedIndex] || null;
719
+ setPageIndex(boundedIndex);
720
+ setMaxAccessiblePageIndex((current) => Math.max(current, boundedIndex));
721
+ if ((nextPage === null || nextPage === void 0 ? void 0 : nextPage.key) && onPageChangeRef.current) {
722
+ onPageChangeRef.current(nextPage.key);
723
+ }
724
+ }, [pageItems]);
725
+ const goToPreviousPage = React.useCallback(() => {
726
+ setPageIndex((current) => {
727
+ const nextIndex = Math.max(current - 1, 0);
728
+ const nextPage = pageItems[nextIndex] || null;
729
+ if ((nextPage === null || nextPage === void 0 ? void 0 : nextPage.key) && onPageChangeRef.current) {
730
+ onPageChangeRef.current(nextPage.key);
731
+ }
732
+ return nextIndex;
733
+ });
734
+ }, [pageItems]);
735
+ React.useEffect(() => {
736
+ setPageIndex(0);
737
+ setMaxAccessiblePageIndex(0);
738
+ setPendingAdvanceFromPageKey("");
739
+ lastRestoredPageKeyRef.current = "";
740
+ }, [survey === null || survey === void 0 ? void 0 : survey.version_id]);
741
+ React.useEffect(() => {
742
+ if (!currentPageKey || currentPageKey === lastRestoredPageKeyRef.current) {
743
+ return;
744
+ }
745
+ const nextIndex = pageItems.findIndex((item) => item.key === currentPageKey);
746
+ if (nextIndex >= 0) {
747
+ lastRestoredPageKeyRef.current = currentPageKey;
748
+ setMaxAccessiblePageIndex((current) => Math.max(current, nextIndex));
749
+ navigateToIndex(nextIndex);
750
+ }
751
+ }, [currentPageKey, navigateToIndex, pageItems]);
752
+ React.useEffect(() => {
753
+ if (pageIndex > Math.max(pageItems.length - 1, 0)) {
754
+ setPageIndex(Math.max(pageItems.length - 1, 0));
755
+ }
756
+ }, [pageIndex, pageItems.length]);
757
+ React.useEffect(() => {
758
+ setMaxAccessiblePageIndex((current) => Math.min(current, Math.max(pageItems.length - 1, 0)));
759
+ }, [pageItems.length]);
760
+ React.useEffect(() => {
761
+ if (!pendingAdvanceFromPageKey) {
762
+ return;
763
+ }
764
+ const sourceIndex = pageItems.findIndex((item) => item.key === pendingAdvanceFromPageKey);
765
+ if (sourceIndex < 0) {
766
+ setPendingAdvanceFromPageKey("");
767
+ return;
768
+ }
769
+ setPendingAdvanceFromPageKey("");
770
+ navigateToIndex(Math.min(sourceIndex + 1, Math.max(pageItems.length - 1, 0)));
771
+ }, [navigateToIndex, pageItems, pendingAdvanceFromPageKey]);
772
+ React.useEffect(() => {
773
+ var _a;
774
+ if (pageIndex === previousPageIndexRef.current) {
775
+ return;
776
+ }
777
+ previousPageIndexRef.current = pageIndex;
778
+ (_a = pageTopRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
779
+ behavior: "smooth",
780
+ block: "start",
781
+ });
782
+ }, [pageIndex]);
783
+ const currentPage = pageItems[pageIndex] || null;
784
+ const currentPageStagedGroupKey = getPageStagedGroupKey(currentPage, questionUiConfig, groupsByName);
785
+ const isLastPage = pageIndex === pageItems.length - 1;
786
+ const disableTouchPageSwipe = pageHasDiscoverySelect(currentPage, questionUiConfig);
787
+ const progressValue = pageItems.length > 0 ? ((pageIndex + 1) / pageItems.length) * 100 : 0;
788
+ const currentPageHasErrors = enforceValidation
789
+ ? getPageErrorKeys(currentPage).some((errorKey) => Boolean(errors === null || errors === void 0 ? void 0 : errors[errorKey]))
790
+ : false;
791
+ const submitErrorSummary = submitAttempted
792
+ ? buildSubmitErrorSummary({
793
+ answers,
794
+ errors: summaryErrors,
795
+ language,
796
+ pageItems,
797
+ t,
798
+ })
799
+ : [];
800
+ const attemptNextPage = React.useCallback(async () => {
801
+ if (isLastPage || !(currentPage === null || currentPage === void 0 ? void 0 : currentPage.key)) {
802
+ return;
803
+ }
804
+ if (onAttemptNextPage) {
805
+ const shouldContinue = await onAttemptNextPage({
806
+ currentPage,
807
+ pageErrorKeys: getPageErrorKeys(currentPage),
808
+ stagedGroupKey: currentPageStagedGroupKey,
809
+ });
810
+ if (shouldContinue === false) {
811
+ return;
812
+ }
813
+ }
814
+ setPendingAdvanceFromPageKey(currentPage.key);
815
+ }, [currentPage, currentPageStagedGroupKey, isLastPage, onAttemptNextPage]);
816
+ React.useEffect(() => {
817
+ const handleKeyDown = (event) => {
818
+ var _a, _b, _c;
819
+ const tagName = (_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.tagName) === null || _b === void 0 ? void 0 : _b.toLowerCase();
820
+ const isEditable = ((_c = event.target) === null || _c === void 0 ? void 0 : _c.isContentEditable) ||
821
+ ["input", "textarea", "select"].includes(tagName);
822
+ if (isEditable) {
823
+ return;
824
+ }
825
+ if (event.key === "ArrowLeft") {
826
+ event.preventDefault();
827
+ goToPreviousPage();
828
+ }
829
+ if (event.key === "ArrowRight" && !isLastPage) {
830
+ event.preventDefault();
831
+ void attemptNextPage();
832
+ }
833
+ };
834
+ window.addEventListener("keydown", handleKeyDown);
835
+ return () => window.removeEventListener("keydown", handleKeyDown);
836
+ }, [attemptNextPage, goToPreviousPage, isLastPage]);
837
+ const handlePageJumpChange = React.useCallback((event) => {
838
+ const nextIndex = Number(event.target.value);
839
+ // The free-jump relaxation below is a builder-only affordance (gated on `neutral`, the
840
+ // additive preview flag) -- NOT on `enforceValidation`, which is a real per-survey setting
841
+ // that also reaches the live public runtime. Gating on enforceValidation instead would let
842
+ // any published survey with validation disabled skip this bound for real respondents too.
843
+ if (!Number.isFinite(nextIndex) || (!neutral && nextIndex > maxAccessiblePageIndex)) {
844
+ return;
845
+ }
846
+ navigateToIndex(nextIndex);
847
+ }, [maxAccessiblePageIndex, navigateToIndex, neutral]);
848
+ const currentSectionTitleQuestion = (currentPage === null || currentPage === void 0 ? void 0 : currentPage.pageType) === "section"
849
+ ? (currentPage.questions || []).find((question) => isTitleNote(question, language, answers))
850
+ : null;
851
+ if (loading) {
852
+ return (_jsx(Stack, { alignItems: "center", py: 4, children: _jsx(CircularProgress, {}) }));
853
+ }
854
+ if (!hasDefinition) {
855
+ return _jsx(Alert, { severity: "warning", children: description });
856
+ }
857
+ return (_jsx(Card, { onTouchEnd: (event) => {
858
+ var _a;
859
+ if (disableTouchPageSwipe) {
860
+ touchStartRef.current = null;
861
+ return;
862
+ }
863
+ const touchStart = touchStartRef.current;
864
+ const touch = (_a = event.changedTouches) === null || _a === void 0 ? void 0 : _a[0];
865
+ if (!touchStart || !touch) {
866
+ return;
867
+ }
868
+ const deltaX = touch.clientX - touchStart.x;
869
+ const deltaY = touch.clientY - touchStart.y;
870
+ if (Math.abs(deltaX) < 60 || Math.abs(deltaX) <= Math.abs(deltaY)) {
871
+ return;
872
+ }
873
+ if (deltaX > 0) {
874
+ goToPreviousPage();
875
+ return;
876
+ }
877
+ if (!isLastPage) {
878
+ void attemptNextPage();
879
+ }
880
+ }, onTouchStart: (event) => {
881
+ var _a;
882
+ if (disableTouchPageSwipe) {
883
+ touchStartRef.current = null;
884
+ return;
885
+ }
886
+ const touch = (_a = event.touches) === null || _a === void 0 ? void 0 : _a[0];
887
+ if (!touch) {
888
+ return;
889
+ }
890
+ touchStartRef.current = { x: touch.clientX, y: touch.clientY };
891
+ }, sx: {
892
+ backgroundColor: neutral ? "background.paper" : "rgba(255, 243, 249, 0.96)",
893
+ border: "none",
894
+ boxShadow: "none",
895
+ maxWidth: "100%",
896
+ overflow: "visible",
897
+ overflowX: "clip",
898
+ pb: { xs: 2.5, md: 3 },
899
+ pt: { xs: 0.25, md: 0.5 },
900
+ px: { xs: 1, sm: 1.5, md: 3 },
901
+ }, children: _jsxs(Stack, { spacing: 3, sx: { minWidth: 0 }, children: [_jsx(Box, { ref: pageTopRef }), _jsxs(Box, { sx: { minWidth: 0 }, children: [!hideSurveyTitle ? (_jsx(Typography, { gutterBottom: true, sx: {
902
+ overflowWrap: "anywhere",
903
+ wordBreak: "break-word",
904
+ }, variant: "h4", children: (survey === null || survey === void 0 ? void 0 : survey.title) || "" })) : null, description ? (_jsx(Typography, { color: "text.secondary", sx: {
905
+ overflowWrap: "anywhere",
906
+ whiteSpace: "pre-line",
907
+ wordBreak: "break-word",
908
+ }, children: description })) : null] }), (currentPage === null || currentPage === void 0 ? void 0 : currentPage.pageType) === "section" ? (() => {
909
+ var _a, _b;
910
+ const groupToolbar = renderGroupToolbar === null || renderGroupToolbar === void 0 ? void 0 : renderGroupToolbar(currentPage.section);
911
+ const GroupRenderer = resolveGroupRenderer(currentPage.section);
912
+ const groupContent = GroupRenderer ? (_jsx(GroupRenderer, { answers: answers, contextAnswers: answers, errors: errors, groupsByName: groupsByName, hideDuplicateIntroTitle: hideDuplicateIntroTitle, language: language, neutral: neutral, onBlurSave: onBlurSave, onChange: onChange, optionsByName: optionsByName, getQuestionDragProps: getQuestionDragProps, questionUiConfig: questionUiConfig, questions: currentPage.questions, renderOptionOverlay: renderOptionOverlay, renderQuestionToolbar: renderQuestionToolbar, section: currentPage.section, serverVisibleQuestionNames: serverVisibleQuestionNames, mediaUrl: mediaUrl, surveyTitle: (survey === null || survey === void 0 ? void 0 : survey.title) || "" })) : currentPage.questions
913
+ .filter((question) => {
914
+ if (question.other_of) {
915
+ return false;
916
+ }
917
+ if (currentSectionTitleQuestion && question.name === currentSectionTitleQuestion.name) {
918
+ return false;
919
+ }
920
+ return isQuestionVisible(question, answers, serverVisibleQuestionNames, questionUiConfig, groupsByName);
921
+ })
922
+ .map((question) => {
923
+ const questionToolbar = renderQuestionToolbar === null || renderQuestionToolbar === void 0 ? void 0 : renderQuestionToolbar(question);
924
+ const questionDragProps = (getQuestionDragProps === null || getQuestionDragProps === void 0 ? void 0 : getQuestionDragProps(question)) || {};
925
+ const questionContent = (_jsx(QuestionField, { answers: answers, contextAnswers: answers, errors: errors, groupsByName: groupsByName, hideDuplicateIntroTitle: hideDuplicateIntroTitle, language: language, neutral: neutral, onBlurSave: onBlurSave, onChange: onChange, optionsByName: optionsByName, question: question, questionUiConfig: questionUiConfig, mediaUrl: mediaUrl, renderOptionOverlay: renderOptionOverlay, surveyTitle: (survey === null || survey === void 0 ? void 0 : survey.title) || "" }));
926
+ if (!neutral && !questionToolbar && Object.keys(questionDragProps).length === 0) {
927
+ return _jsx("div", { "data-question-name": question.name, children: questionContent }, question.name);
928
+ }
929
+ return (_jsxs(Box, Object.assign({ component: "div", "data-question-name": question.name, sx: {
930
+ backgroundColor: neutral ? "background.default" : undefined,
931
+ border: neutral ? "1px solid" : undefined,
932
+ borderColor: neutral ? "divider" : undefined,
933
+ borderRadius: neutral ? 2 : undefined,
934
+ p: neutral ? { xs: 1.25, sm: 1.5 } : undefined,
935
+ "&:hover, &:focus-within": neutral ? { borderColor: "primary.main" } : undefined,
936
+ } }, questionDragProps, { children: [questionToolbar, questionContent] }), question.name));
937
+ });
938
+ return (_jsxs(Box, { component: "div", "data-group-name": (_a = currentPage.section) === null || _a === void 0 ? void 0 : _a.name, sx: neutral ? {
939
+ backgroundColor: "background.paper",
940
+ border: "1px solid",
941
+ borderColor: "divider",
942
+ borderLeft: "3px solid",
943
+ borderLeftColor: "primary.main",
944
+ borderRadius: 2,
945
+ p: { xs: 1.5, sm: 2 },
946
+ } : undefined, children: [groupToolbar, neutral && currentPage.title ? (_jsxs(Stack, { alignItems: "center", direction: "row", spacing: 1, sx: { mb: 2 }, children: [_jsx(Typography, { variant: "subtitle1", children: currentPage.title }), ((_b = currentPage.section) === null || _b === void 0 ? void 0 : _b.appearance) === "field-list" ? _jsx(Chip, { color: "primary", label: t("AiBuilder.PAGE_BADGE", { defaultValue: "Page" }), size: "small", variant: "outlined" }) : null] })) : null, _jsx(Stack, { spacing: 2.5, children: groupContent })] }, currentPage.key));
947
+ })() : null, (currentPage === null || currentPage === void 0 ? void 0 : currentPage.pageType) === "repeat" ? (_jsx(RepeatSection, { answers: answers, entryIndex: currentPage.repeatIndex, errors: errors, language: language, mediaUrl: mediaUrl, onBlurSave: onBlurSave, onChange: onChange, onRepeatAdd: onRepeatAdd, onRepeatRemove: onRepeatRemove, optionsByName: optionsByName, questionUiConfig: questionUiConfig, neutral: neutral, renderOptionOverlay: renderOptionOverlay, repeatEntries: answers[currentPage.section.name] || [], repeatEvaluation: (_a = evaluation.repeats) === null || _a === void 0 ? void 0 : _a[currentPage.section.name], section: currentPage.section }, currentPage.key)) : null, _jsxs(Stack, { spacing: 1.25, sx: { pt: 2 }, children: [submitErrorSummary.length > 0 ? (_jsx(Alert, { severity: "error", children: _jsxs(Stack, { spacing: 1.25, children: [_jsx(Typography, { fontWeight: 700, children: t("surveyRenderer.SUBMIT_ERRORS_TITLE") }), _jsx(Typography, { variant: "body2", children: t("surveyRenderer.SUBMIT_ERRORS_BODY") }), _jsx(Stack, { direction: { xs: "column", sm: "row" }, flexWrap: "wrap", gap: 1, children: submitErrorSummary.map((entry) => (_jsx(Button, { onClick: () => navigateToIndex(entry.index), size: "small", sx: { alignSelf: { xs: "stretch", sm: "flex-start" } }, variant: "outlined", children: `${entry.label} (${entry.errorCount})` }, entry.key))) })] }) })) : null, currentPageHasErrors ? (_jsx(Alert, { severity: "warning", children: t("surveyRenderer.FIX_ERRORS_BEFORE_CONTINUE") })) : null, _jsxs(Stack, { direction: "row", spacing: 1.5, children: [_jsx(Button, { disabled: pageIndex === 0, onClick: goToPreviousPage, sx: neutral ? { flex: 1, minHeight: 56 } : {
948
+ backgroundColor: "#f5bfd8",
949
+ color: "#7a2455",
950
+ flex: 1,
951
+ minHeight: 56,
952
+ "&:hover": {
953
+ backgroundColor: "#eeabc9",
954
+ },
955
+ }, variant: neutral ? "outlined" : "contained", children: t("surveyRenderer.PREVIOUS_QUESTION", {
956
+ defaultValue: "Back to the previous question",
957
+ }) }), !isLastPage ? (_jsx(Button, { onClick: () => {
958
+ void attemptNextPage();
959
+ }, sx: {
960
+ flex: 1,
961
+ minHeight: 56,
962
+ }, variant: "contained", children: t("surveyRenderer.NEXT_QUESTION", {
963
+ defaultValue: "Continue to the next question",
964
+ }) })) : (_jsx(Button, { disabled: submitting, onClick: onSubmit, sx: {
965
+ flex: 1,
966
+ minHeight: 56,
967
+ }, variant: "contained", children: submitLabel }))] }), callToAction] }), _jsxs(Box, { sx: {
968
+ alignItems: { xs: "stretch", md: "center" },
969
+ display: "grid",
970
+ gap: 1.25,
971
+ gridTemplateColumns: { xs: "1fr", md: "minmax(0, 1fr) 260px" },
972
+ }, children: [_jsx(LinearProgress, { color: "primary", sx: {
973
+ backgroundColor: "rgba(17, 17, 17, 0.08)",
974
+ borderRadius: 999,
975
+ height: 10,
976
+ "& .MuiLinearProgress-bar": {
977
+ borderRadius: 999,
978
+ },
979
+ }, value: progressValue, variant: "determinate" }), pageItems.length > 1 && neutral ? (_jsx(Stack, { direction: "row", flexWrap: "wrap", gap: 1, children: pageItems.map((pageItem, index) => (_jsx(Button, { "aria-current": index === pageIndex ? "page" : undefined, onClick: () => navigateToIndex(index), size: "small", sx: { borderRadius: 999 }, variant: index === pageIndex ? "contained" : "outlined", children: getPageNavigationLabel(pageItem, index, language, answers, t) }, pageItem.key))) })) : pageItems.length > 1 ? (_jsx(TextField, { fullWidth: true, label: t("surveyRenderer.PAGE_JUMP"), onChange: handlePageJumpChange, select: true, size: "small", sx: {
980
+ ".MuiOutlinedInput-root": {
981
+ backgroundColor: neutral ? "background.paper" : "rgba(255, 243, 249, 0.96)",
982
+ },
983
+ }, value: String(pageIndex), children: pageItems.map((pageItem, index) => (_jsx(MenuItem, { disabled: index > maxAccessiblePageIndex, value: String(index), children: getPageNavigationLabel(pageItem, index, language, answers, t) }, pageItem.key))) })) : null] })] }) }));
984
+ };
985
+ export default SurveyRenderer;