@opengovsg/confetti 0.0.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.js ADDED
@@ -0,0 +1,1155 @@
1
+ import { ErrorBoundary } from "react-error-boundary";
2
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
3
+ import { z } from "zod/v4/mini";
4
+ import { useLocalStorage, useTimeout } from "usehooks-ts";
5
+ import ky from "ky";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import clsx from "clsx";
8
+ import { Button, Checkbox, CheckboxGroup, FieldError, Input, Label, Radio, RadioGroup, Text, TextArea, TextField, composeRenderProps } from "react-aria-components";
9
+
10
+ //#region src/logic/route.ts
11
+ const isVisible = ({ index, questions, answers }) => {
12
+ if (index === 0) return true;
13
+ for (let i = 0; i < index; i++) {
14
+ const question = questions[i];
15
+ const answer = question ? answers[question.id] : void 0;
16
+ if (!question || !question.visible) continue;
17
+ const logic = question.routingLogic;
18
+ if (logic.type === "always") if (evaluateTarget({
19
+ position: i,
20
+ value: logic.target
21
+ }) === index) return true;
22
+ else continue;
23
+ if ((answer === void 0 || answer === null) && question.required) continue;
24
+ const cases = logic.cases;
25
+ const match = answer !== null ? cases.find((condition) => evaluateCondition({
26
+ answer,
27
+ condition
28
+ })) : void 0;
29
+ if (!match) if (evaluateTarget({
30
+ position: i,
31
+ value: logic.default
32
+ }) === index) return true;
33
+ else continue;
34
+ if (evaluateTarget({
35
+ position: i,
36
+ value: match.target
37
+ }) === index) return true;
38
+ else continue;
39
+ }
40
+ return false;
41
+ };
42
+ const evaluateTarget = ({ position, value }) => {
43
+ switch (value.type) {
44
+ case "next": return position + 1;
45
+ case "jump": return value.target;
46
+ case "end": return null;
47
+ }
48
+ };
49
+ const evaluateCondition = ({ answer, condition }) => {
50
+ const value = evaluateValue(condition.value);
51
+ switch (condition.operator) {
52
+ case "equal": return answer === value;
53
+ case "not-equal": return answer !== value;
54
+ case "greater": return typeof answer === "number" && typeof value === "number" && answer > value;
55
+ case "greater-or-equal": return typeof answer === "number" && typeof value === "number" && answer >= value;
56
+ case "less": return typeof answer === "number" && typeof value === "number" && answer < value;
57
+ case "less-or-equal": return typeof answer === "number" && typeof value === "number" && answer <= value;
58
+ case "contains": return Array.isArray(answer) && typeof value === "string" && answer.includes(value);
59
+ case "not-contains": return Array.isArray(answer) && typeof value === "string" && !answer.includes(value);
60
+ }
61
+ };
62
+ const evaluateValue = (node) => {
63
+ return node.value;
64
+ };
65
+
66
+ //#endregion
67
+ //#region ../validators/src/questions/routing.ts
68
+ const nextTargetSchema = z.object({ type: z.literal("next") });
69
+ const endTargetSchema = z.object({ type: z.literal("end") });
70
+ const jumpTargetSchema = z.object({
71
+ type: z.literal("jump"),
72
+ target: z.number({ error: "Target question is required" })
73
+ });
74
+ const conditionTargetSchema = z.discriminatedUnion("type", [
75
+ nextTargetSchema,
76
+ jumpTargetSchema,
77
+ endTargetSchema
78
+ ]);
79
+ const valueNode = z.object({
80
+ type: z.literal("value"),
81
+ value: z.union([z.string().check(z.trim(), z.minLength(1, { error: "Value is required" })), z.number({ error: "Value is required" })])
82
+ });
83
+ const binaryOperatorConditionSchema = z.object({
84
+ type: z.literal("binary-operator"),
85
+ operator: z.union([
86
+ z.literal("equal"),
87
+ z.literal("not-equal"),
88
+ z.literal("greater"),
89
+ z.literal("greater-or-equal"),
90
+ z.literal("less"),
91
+ z.literal("less-or-equal"),
92
+ z.literal("contains"),
93
+ z.literal("not-contains")
94
+ ]),
95
+ value: valueNode,
96
+ target: conditionTargetSchema
97
+ });
98
+ const switchConditionSchema = z.object({
99
+ type: z.literal("switch"),
100
+ cases: z.array(binaryOperatorConditionSchema),
101
+ default: conditionTargetSchema
102
+ });
103
+ const alwaysConditionSchema = z.object({
104
+ type: z.literal("always"),
105
+ target: conditionTargetSchema
106
+ });
107
+ const routingLogicSchema = z.discriminatedUnion("type", [alwaysConditionSchema, switchConditionSchema]);
108
+
109
+ //#endregion
110
+ //#region ../validators/src/questions/properties.ts
111
+ const optionSchema = z.object({
112
+ label: z.string().check(z.trim(), z.minLength(1, { error: "Label is required" })),
113
+ value: z.string().check(z.trim(), z.minLength(1, { error: "Value is required" }))
114
+ });
115
+ const editableSchema = z.nullable(z.object({
116
+ label: z.string().check(z.trim(), z.minLength(1, { error: "Label is required" })),
117
+ max: z.number()
118
+ }));
119
+ const ratingQuestionSchema = z.object({
120
+ type: z.literal("rating"),
121
+ scale: z.number().check(z.minimum(2), z.maximum(10)),
122
+ labels: z.optional(z.object({
123
+ lower: z.string().check(z.trim(), z.minLength(1, { error: "The rating label is required" })),
124
+ upper: z.string().check(z.trim(), z.minLength(1, { error: "The rating label is required" }))
125
+ })),
126
+ display: z.optional(z.literal([
127
+ "star",
128
+ "emoji",
129
+ "number"
130
+ ]))
131
+ });
132
+ const textQuestionSchema = z.object({
133
+ type: z.literal("open-ended"),
134
+ max: z.number()
135
+ });
136
+ const singleSelectQuestionSchema = z.object({
137
+ type: z.literal("single-select"),
138
+ options: z.array(optionSchema),
139
+ editable: editableSchema
140
+ });
141
+ const multiSelectQuestionSchema = z.object({
142
+ type: z.literal("multi-select"),
143
+ options: z.array(optionSchema),
144
+ editable: editableSchema,
145
+ max: z.optional(z.number())
146
+ });
147
+ const questionPropertiesSchema = z.discriminatedUnion("type", [
148
+ ratingQuestionSchema,
149
+ singleSelectQuestionSchema,
150
+ textQuestionSchema,
151
+ multiSelectQuestionSchema
152
+ ]);
153
+
154
+ //#endregion
155
+ //#region ../validators/src/questions/constants.ts
156
+ const CHECKBOX_OTHERS_VALUE = "__CONFETTI_CHECKBOX_Others";
157
+ const RADIO_OTHERS_VALUE = "__CONFETTI_RADIO_Others";
158
+
159
+ //#endregion
160
+ //#region ../validators/src/questions/answers.ts
161
+ const baseRatingAnswerSchema = (scale) => z.number().check(z.int(), z.minimum(1), z.maximum(scale));
162
+ const ratingAnswerSchemaFactory = ({ scale }, { required }) => {
163
+ const baseSchema = baseRatingAnswerSchema(scale);
164
+ return required ? baseSchema : z.optional(baseSchema);
165
+ };
166
+ const openEndedAnswerSchemaFactory = ({ maxLength }, { required }) => z.string().check(z.trim(), ...required ? [z.minLength(1, { error: "This field is required" })] : [], z.maxLength(maxLength ?? 2048, { error: ({ maximum }) => `Please enter less than ${maximum} characters` }));
167
+ const baseSingleSelectAnswerSchema = (maxLength, required) => z.string().check(z.trim(), ...required ? [z.minLength(1, { error: "This field is required" })] : [], z.maxLength(maxLength ?? 1024, { error: ({ maximum }) => `Please enter less than ${maximum} characters` }), z.refine((val) => val !== RADIO_OTHERS_VALUE, { error: "Please fill out the free text field" }));
168
+ const singleSelectAnswerSchemaFactory = ({ maxLength }, { required }) => {
169
+ const baseSchema = baseSingleSelectAnswerSchema(maxLength, required);
170
+ return required ? baseSchema : z.optional(baseSchema);
171
+ };
172
+ const multiSelectAnswerSchemaFactory = ({ maxSelected, maxLength }, { required }) => z.array(z.string().check(z.trim(), ...required ? [z.minLength(1, { error: "This field is required" })] : [], z.maxLength(maxLength ?? 1024, { error: ({ maximum }) => `Please enter less than ${maximum} characters` }), z.refine((val) => val !== CHECKBOX_OTHERS_VALUE, { error: "Please fill out the free text field" }))).check(...required ? [z.minLength(1, { error: "Please select at least one option" })] : [], z.maxLength(maxSelected ?? 1024, { error: ({ maximum }) => `Please select less than ${maximum} options` }));
173
+
174
+ //#endregion
175
+ //#region ../validators/src/survey.ts
176
+ const publicGetSurveyOutputSchema = z.object({
177
+ title: z.string(),
178
+ description: z.nullable(z.string()),
179
+ questions: z.array(z.object({
180
+ id: z.string(),
181
+ title: z.string(),
182
+ description: z.nullable(z.string()),
183
+ position: z.number(),
184
+ required: z.boolean(),
185
+ properties: questionPropertiesSchema,
186
+ routingLogic: routingLogicSchema
187
+ }))
188
+ });
189
+
190
+ //#endregion
191
+ //#region ../validators/src/response.ts
192
+ const publicPutResponseInputSchema = z.object({
193
+ respondent: z.string().check(z.trim(), z.minLength(1), z.maxLength(2048)),
194
+ data: z.record(z.string(), z.union([
195
+ z.string().check(z.trim(), z.maxLength(2048)),
196
+ z.number(),
197
+ z.array(z.string().check(z.trim(), z.maxLength(2048))).check(z.maxLength(1024))
198
+ ])),
199
+ metadata: z.record(z.string().check(z.trim(), z.maxLength(1e3)), z.string().check(z.trim(), z.maxLength(1e3)))
200
+ });
201
+ const publicGetRespondentOutputSchema = z.object({ lastRespondedAt: z.nullable(z.union([z.iso.datetime(), z.date()])) });
202
+
203
+ //#endregion
204
+ //#region src/logic/validate.ts
205
+ const VALIDATOR_FACTORY_MAP = {
206
+ rating: ({ scale }, meta) => ratingAnswerSchemaFactory({ scale }, meta),
207
+ "open-ended": ({ max }, meta) => openEndedAnswerSchemaFactory({ maxLength: max }, meta),
208
+ "single-select": ({ editable }, meta) => singleSelectAnswerSchemaFactory({ maxLength: editable?.max }, meta),
209
+ "multi-select": ({ editable, max }, meta) => multiSelectAnswerSchemaFactory({
210
+ maxLength: editable?.max,
211
+ maxSelected: max
212
+ }, meta)
213
+ };
214
+ const getAnswerValidator = (properties, meta) => {
215
+ const type = properties.type;
216
+ switch (type) {
217
+ case "rating": {
218
+ const validator = VALIDATOR_FACTORY_MAP[type];
219
+ return validator(properties, meta);
220
+ }
221
+ case "open-ended": {
222
+ const validator = VALIDATOR_FACTORY_MAP[type];
223
+ return validator(properties, meta);
224
+ }
225
+ case "single-select": {
226
+ const validator = VALIDATOR_FACTORY_MAP[type];
227
+ return validator(properties, meta);
228
+ }
229
+ case "multi-select": {
230
+ const validator = VALIDATOR_FACTORY_MAP[type];
231
+ return validator(properties, meta);
232
+ }
233
+ default: throw new Error("Unsupported question type");
234
+ }
235
+ };
236
+
237
+ //#endregion
238
+ //#region src/utils.ts
239
+ function prettifyError(error) {
240
+ return error.issues[0]?.message ?? "Invalid input";
241
+ }
242
+
243
+ //#endregion
244
+ //#region src/logic/api.ts
245
+ const API_HOST = "https://confetti.hack2026.gov.sg/api/cfti";
246
+ const client = ky.create({
247
+ prefixUrl: API_HOST,
248
+ retry: {
249
+ limit: 3,
250
+ methods: ["get"]
251
+ },
252
+ timeout: 6e4
253
+ });
254
+
255
+ //#endregion
256
+ //#region src/components/ConfettiProvider.tsx
257
+ const ConfettiContext = createContext(null);
258
+ const ConfettiProvider = ({ children, surveyId, publishableKey, respondent, metadata, isSurveyVisible: isSurveyVisibleInput, apiBaseUrl }) => {
259
+ const [survey, setSurvey] = useState();
260
+ const [lastRespondedAt, setLastRespondedAt] = useState();
261
+ const responseId = useMemo(() => crypto.randomUUID(), []);
262
+ const [internalRespondentId, setInternalRespondentId] = useLocalStorage(`cfti:${surveyId}:respondent`, "");
263
+ useEffect(() => {
264
+ if (internalRespondentId) return;
265
+ setInternalRespondentId(crypto.randomUUID());
266
+ }, [internalRespondentId, setInternalRespondentId]);
267
+ const client$1 = useMemo(() => {
268
+ const properties = { headers: { "x-cfti-pk": publishableKey } };
269
+ if (apiBaseUrl) properties.prefixUrl = apiBaseUrl;
270
+ return client.extend(properties);
271
+ }, [apiBaseUrl, publishableKey]);
272
+ useEffect(() => {
273
+ client$1.get(`${surveyId}`).json().then((data) => {
274
+ const parseResult = publicGetSurveyOutputSchema.safeParse(data);
275
+ if (!parseResult.success) return;
276
+ setSurvey(parseResult.data);
277
+ });
278
+ if (!respondent && !internalRespondentId) return;
279
+ client$1.get(`${surveyId}/respondent/${respondent ?? internalRespondentId}`).json().then((data) => {
280
+ const parseResult = publicGetRespondentOutputSchema.safeParse(data);
281
+ if (!parseResult.success) {
282
+ setLastRespondedAt(null);
283
+ return;
284
+ }
285
+ setLastRespondedAt(parseResult.data.lastRespondedAt ? new Date(parseResult.data.lastRespondedAt) : null);
286
+ });
287
+ }, [
288
+ apiBaseUrl,
289
+ client$1,
290
+ internalRespondentId,
291
+ respondent,
292
+ surveyId
293
+ ]);
294
+ const submit = useCallback(async ({ answers }) => {
295
+ await client$1.put(`${surveyId}/response/${responseId}`, { json: {
296
+ respondent: respondent ?? internalRespondentId,
297
+ data: answers,
298
+ metadata
299
+ } });
300
+ }, [
301
+ client$1,
302
+ internalRespondentId,
303
+ metadata,
304
+ respondent,
305
+ responseId,
306
+ surveyId
307
+ ]);
308
+ const dismiss = useCallback(() => {
309
+ setSurvey(void 0);
310
+ }, []);
311
+ if (!useMemo(() => {
312
+ if (isSurveyVisibleInput === void 0) return true;
313
+ if (typeof isSurveyVisibleInput === "boolean") return isSurveyVisibleInput;
314
+ if (lastRespondedAt === void 0) return false;
315
+ return isSurveyVisibleInput({ lastRespondedAt });
316
+ }, [isSurveyVisibleInput, lastRespondedAt]) || !survey) return null;
317
+ return /* @__PURE__ */ jsx(ConfettiContext.Provider, {
318
+ value: {
319
+ submit,
320
+ dismiss,
321
+ surveyId,
322
+ survey,
323
+ metadata
324
+ },
325
+ children
326
+ });
327
+ };
328
+ const useConfetti = () => {
329
+ const context = useContext(ConfettiContext);
330
+ if (!context) throw new Error("useConfetti must be used within a ConfettiProvider");
331
+ return context;
332
+ };
333
+
334
+ //#endregion
335
+ //#region src/components/ConfettiController.tsx
336
+ const getNextQuestionState = ({ originalQuestions, answers, errors, pointer = -1 }) => {
337
+ if (!originalQuestions) return {
338
+ questions: [],
339
+ currentIndex: -1
340
+ };
341
+ const questions = originalQuestions.sort((a, b) => a.position - b.position).map((question) => {
342
+ const obj = { ...question };
343
+ const error = errors[question.id];
344
+ if (error) obj.error = error;
345
+ return {
346
+ ...obj,
347
+ visible: false
348
+ };
349
+ });
350
+ for (let i = 0; i < questions.length; i++) {
351
+ const question = questions[i];
352
+ if (!question) continue;
353
+ question.visible = isVisible({
354
+ index: i,
355
+ questions,
356
+ answers
357
+ });
358
+ }
359
+ let nextIndex = questions.findIndex((q, i) => i > pointer && q.visible && answers[q.id] === void 0);
360
+ if (nextIndex === -1) nextIndex = questions.findLastIndex((q) => !!q.visible);
361
+ return {
362
+ questions,
363
+ currentIndex: nextIndex
364
+ };
365
+ };
366
+ const getSubmittableAnswers = ({ questions, answers }) => {
367
+ return Object.fromEntries(Object.entries(answers).filter(([id]) => !!questions.find((question) => question.id === id)?.visible).filter(([, answer]) => answer !== null));
368
+ };
369
+ const ConfettiController = ({ current: currentProp, shouldAutoSubmit, onFinished, children }) => {
370
+ const { survey, submit } = useConfetti();
371
+ const [answers, setAnswers] = useState({});
372
+ const [errors, setErrors] = useState({});
373
+ const state = useMemo(() => getNextQuestionState({
374
+ originalQuestions: survey?.questions,
375
+ answers,
376
+ errors,
377
+ pointer: currentProp
378
+ }), [
379
+ survey?.questions,
380
+ answers,
381
+ errors,
382
+ currentProp
383
+ ]);
384
+ const isCompleted = useMemo(() => {
385
+ return !!state.questions.filter(({ required, visible }) => !!required && !!visible).every(({ id }) => answers[id] !== void 0);
386
+ }, [answers, state.questions]);
387
+ const [isSubmitted, setIsSubmitted] = useState(false);
388
+ const handleSubmitWithFinish = useCallback(({ answers: answers$1 }) => {
389
+ submit({ answers: answers$1 }).finally(() => {
390
+ onFinished?.();
391
+ });
392
+ }, [onFinished, submit]);
393
+ const handleSubmit = useCallback(() => {
394
+ handleSubmitWithFinish({ answers: getSubmittableAnswers({
395
+ questions: state.questions,
396
+ answers
397
+ }) });
398
+ setIsSubmitted(true);
399
+ }, [
400
+ answers,
401
+ state.questions,
402
+ handleSubmitWithFinish
403
+ ]);
404
+ const update = useCallback(({ question: index, answer }) => {
405
+ const question = state.questions.at(index);
406
+ if (!question) return { error: "Question index is invalid" };
407
+ const parseResult = getAnswerValidator(question.properties, { required: question.required }).safeParse(answer);
408
+ if (!parseResult.success) {
409
+ const prettifiedError = prettifyError(parseResult.error);
410
+ setErrors((prev) => {
411
+ if (prev[question.id] === prettifiedError) return prev;
412
+ const copy = { ...prev };
413
+ copy[question.id] = prettifiedError;
414
+ return copy;
415
+ });
416
+ return { error: prettifiedError };
417
+ }
418
+ const internalAnswer = !question.required && !answer ? null : answer;
419
+ const next = { ...answers };
420
+ if (next[question.id] === internalAnswer) return { next: getNextQuestionState({
421
+ originalQuestions: survey?.questions,
422
+ answers: next,
423
+ errors,
424
+ pointer: currentProp
425
+ }).currentIndex };
426
+ next[question.id] = internalAnswer;
427
+ setAnswers(next);
428
+ const nextQuestionState = getNextQuestionState({
429
+ originalQuestions: survey?.questions,
430
+ answers: next,
431
+ errors,
432
+ pointer: currentProp
433
+ });
434
+ if (nextQuestionState.questions.filter(({ visible }) => !!visible).every(({ id }) => next[id] !== void 0) && shouldAutoSubmit) {
435
+ handleSubmitWithFinish({ answers: getSubmittableAnswers({
436
+ questions: nextQuestionState.questions,
437
+ answers: next
438
+ }) });
439
+ setIsSubmitted(true);
440
+ }
441
+ return { next: nextQuestionState.currentIndex };
442
+ }, [
443
+ state.questions,
444
+ answers,
445
+ survey?.questions,
446
+ errors,
447
+ shouldAutoSubmit,
448
+ currentProp,
449
+ handleSubmitWithFinish
450
+ ]);
451
+ if (state.questions.length === 0) return /* @__PURE__ */ jsx(Fragment, {});
452
+ return /* @__PURE__ */ jsx("div", {
453
+ className: "confetti",
454
+ children: children({
455
+ questions: state.questions,
456
+ current: currentProp ?? state.currentIndex,
457
+ update,
458
+ isCompleted,
459
+ submit: handleSubmit,
460
+ isSubmitted,
461
+ errors
462
+ })
463
+ });
464
+ };
465
+
466
+ //#endregion
467
+ //#region src/components/Confetti.tsx
468
+ const Confetti = ({ children, shouldAutoSubmit, ...props }) => {
469
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
470
+ fallback: /* @__PURE__ */ jsx(Fragment, {}),
471
+ children: /* @__PURE__ */ jsx(ConfettiProvider, {
472
+ ...props,
473
+ children: /* @__PURE__ */ jsx(ConfettiController, {
474
+ shouldAutoSubmit,
475
+ children
476
+ })
477
+ })
478
+ });
479
+ };
480
+
481
+ //#endregion
482
+ //#region src/components/ThankYouMessage.tsx
483
+ const ThankYouMessage = ({ title, description, classNames }) => {
484
+ return /* @__PURE__ */ jsx("div", {
485
+ className: clsx("cf-question", classNames?.question),
486
+ children: /* @__PURE__ */ jsxs("div", {
487
+ className: clsx("cf-header", classNames?.header),
488
+ children: [/* @__PURE__ */ jsx(Label, {
489
+ className: clsx("cf-title", classNames?.title),
490
+ children: title ?? "Feedback submitted!"
491
+ }), /* @__PURE__ */ jsx(Text, {
492
+ slot: "description",
493
+ className: clsx("cf-description", classNames?.description),
494
+ children: description ?? "Thank you for your feedback!"
495
+ })]
496
+ })
497
+ });
498
+ };
499
+
500
+ //#endregion
501
+ //#region src/components/container/PopoverContainer.tsx
502
+ const PopoverContainer = ({ children, classNames, containerProps, onDismiss, closeOnFinishMs = 3e3 }) => {
503
+ const { surveyId, dismiss } = useConfetti();
504
+ const [isFinished, setIsFinished] = useState(false);
505
+ useTimeout(() => {
506
+ dismiss();
507
+ }, isFinished ? closeOnFinishMs : null);
508
+ return /* @__PURE__ */ jsxs("div", {
509
+ className: clsx("confetti-dialog", classNames?.dialog),
510
+ id: `${surveyId}-popover`,
511
+ ...containerProps,
512
+ children: [/* @__PURE__ */ jsx("button", {
513
+ type: "button",
514
+ className: clsx("confetti-dialog-close", classNames?.closeButton),
515
+ "aria-label": "Dismiss",
516
+ onClick: async () => {
517
+ await dismiss();
518
+ onDismiss?.();
519
+ },
520
+ children: /* @__PURE__ */ jsx("svg", {
521
+ xmlns: "http://www.w3.org/2000/svg",
522
+ viewBox: "0 0 16 16",
523
+ fill: "none",
524
+ className: clsx(classNames?.icon),
525
+ children: /* @__PURE__ */ jsx("path", {
526
+ fill: "currentColor",
527
+ fillRule: "evenodd",
528
+ d: "M3.53 3.529c.26-.26.682-.26.942 0l3.529 3.528 3.528-3.528a.667.667 0 0 1 .943.942L8.943 8l3.53 3.528a.667.667 0 0 1-.944.943L8.001 8.943 4.472 12.47a.667.667 0 1 1-.943-.943L7.058 8 3.529 4.47a.667.667 0 0 1 0-.942Z",
529
+ clipRule: "evenodd"
530
+ })
531
+ })
532
+ }), typeof children === "function" ? children({ setIsFinished: () => setIsFinished(true) }) : children]
533
+ });
534
+ };
535
+
536
+ //#endregion
537
+ //#region src/components/questions/FreeText.tsx
538
+ const FreeText = ({ id, title, description, properties, onChange, classNames, required, error }) => {
539
+ const [value, setValue] = useState("");
540
+ useEffect(() => {
541
+ onChange(value);
542
+ }, [onChange, value]);
543
+ return /* @__PURE__ */ jsxs(TextField, {
544
+ isRequired: required,
545
+ isInvalid: !!error,
546
+ name: id,
547
+ className: clsx("cf-question", classNames?.question),
548
+ value,
549
+ onChange: (v) => {
550
+ setValue(v);
551
+ onChange(v);
552
+ },
553
+ children: [
554
+ /* @__PURE__ */ jsxs("div", {
555
+ className: clsx("cf-header", classNames?.header),
556
+ children: [/* @__PURE__ */ jsx(Label, {
557
+ className: clsx("cf-title", classNames?.title),
558
+ children: title
559
+ }), description && /* @__PURE__ */ jsx(Text, {
560
+ slot: "description",
561
+ className: clsx("cf-description", classNames?.description),
562
+ children: description
563
+ })]
564
+ }),
565
+ /* @__PURE__ */ jsx(TextArea, {
566
+ className: clsx("cf-textarea", classNames?.textarea),
567
+ maxLength: properties.max,
568
+ rows: 4,
569
+ placeholder: "Start typing..."
570
+ }),
571
+ /* @__PURE__ */ jsx(FieldError, {
572
+ className: clsx("cf-error", classNames?.error),
573
+ children: error
574
+ })
575
+ ]
576
+ });
577
+ };
578
+
579
+ //#endregion
580
+ //#region src/components/questions/MultiChoice.tsx
581
+ const MultiChoiceCheckbox = ({ children, classNames, ...props }) => {
582
+ return /* @__PURE__ */ jsx(Checkbox, {
583
+ ...props,
584
+ children: ({ isIndeterminate }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
585
+ className: clsx("cf-checkbox-indicator", classNames?.indicator),
586
+ children: /* @__PURE__ */ jsx("svg", {
587
+ viewBox: "0 0 18 18",
588
+ "aria-hidden": "true",
589
+ className: clsx("cf-checkbox-indicator-svg", classNames?.svg),
590
+ children: isIndeterminate ? /* @__PURE__ */ jsx("rect", {
591
+ x: 1,
592
+ y: 7.5,
593
+ width: 16,
594
+ height: 3
595
+ }) : /* @__PURE__ */ jsx("polyline", { points: "2 9 7 14 16 4" })
596
+ }, isIndeterminate ? "indeterminate" : "check")
597
+ }), children] })
598
+ });
599
+ };
600
+ const MultiChoice = ({ title, description, properties, classNames, onChange, id, required, error }) => {
601
+ const hasEditable = !!properties.editable;
602
+ const [value, setValue] = useState([]);
603
+ const [othersValue, setOthersValue] = useState("");
604
+ const othersRef = useRef(null);
605
+ const othersInputRef = useRef(null);
606
+ const submissionValue = useMemo(() => {
607
+ const othersValueIndex = value.indexOf(CHECKBOX_OTHERS_VALUE);
608
+ if (othersValueIndex === -1) return value;
609
+ const submissionValue$1 = [...value];
610
+ submissionValue$1[othersValueIndex] = othersValue || CHECKBOX_OTHERS_VALUE;
611
+ return submissionValue$1;
612
+ }, [othersValue, value]);
613
+ useEffect(() => {
614
+ onChange(submissionValue);
615
+ }, [onChange, submissionValue]);
616
+ return /* @__PURE__ */ jsx(CheckboxGroup, {
617
+ isRequired: required,
618
+ isInvalid: !!error,
619
+ name: id,
620
+ value,
621
+ onChange: setValue,
622
+ className: clsx("cf-question", classNames?.question),
623
+ children: ({ state }) => /* @__PURE__ */ jsxs(Fragment, { children: [
624
+ /* @__PURE__ */ jsxs("div", {
625
+ className: clsx("cf-header", classNames?.header),
626
+ children: [/* @__PURE__ */ jsx(Label, {
627
+ className: clsx("cf-title", classNames?.title),
628
+ children: title
629
+ }), description && /* @__PURE__ */ jsx(Text, {
630
+ slot: "description",
631
+ className: clsx("cf-description", classNames?.description),
632
+ children: description
633
+ })]
634
+ }),
635
+ /* @__PURE__ */ jsxs("div", {
636
+ className: clsx("cf-response-choices", classNames?.group),
637
+ children: [properties.options.map((option, index) => /* @__PURE__ */ jsx(MultiChoiceCheckbox, {
638
+ className: clsx("cf-checkbox", classNames?.checkbox),
639
+ value: option.value,
640
+ classNames: {
641
+ indicator: classNames?.indicator,
642
+ svg: classNames?.svg
643
+ },
644
+ children: option.label
645
+ }, index)), hasEditable && /* @__PURE__ */ jsxs("div", {
646
+ className: clsx("cf-others-choice", classNames?.othersChoice),
647
+ children: [/* @__PURE__ */ jsx(MultiChoiceCheckbox, {
648
+ inputRef: othersRef,
649
+ onChange: (isSelected) => {
650
+ if (!othersInputRef.current) return;
651
+ if (isSelected) requestAnimationFrame(() => {
652
+ if (!othersInputRef.current) return;
653
+ othersInputRef.current.focus();
654
+ });
655
+ },
656
+ className: clsx("cf-checkbox", classNames?.checkbox),
657
+ value: CHECKBOX_OTHERS_VALUE,
658
+ classNames: {
659
+ indicator: classNames?.indicator,
660
+ svg: classNames?.svg
661
+ },
662
+ children: properties.editable?.label
663
+ }), /* @__PURE__ */ jsx(Input, {
664
+ value: othersValue,
665
+ onChange: (e) => {
666
+ setOthersValue(e.target.value);
667
+ },
668
+ tabIndex: state.value.includes(CHECKBOX_OTHERS_VALUE) ? 0 : -1,
669
+ ref: othersInputRef,
670
+ onFocus: () => {
671
+ if (othersRef.current?.checked === false) othersRef.current.click();
672
+ },
673
+ className: clsx("cf-input", classNames?.othersInput)
674
+ })]
675
+ })]
676
+ }),
677
+ /* @__PURE__ */ jsx(FieldError, {
678
+ className: clsx("cf-error", classNames?.error),
679
+ children: error
680
+ })
681
+ ] })
682
+ });
683
+ };
684
+
685
+ //#endregion
686
+ //#region src/components/svgr/EmojiAngry.tsx
687
+ const EmojiAngry = (props) => /* @__PURE__ */ jsxs("svg", {
688
+ xmlns: "http://www.w3.org/2000/svg",
689
+ width: 40,
690
+ height: 40,
691
+ viewBox: "0 0 24 24",
692
+ fill: "none",
693
+ stroke: "currentColor",
694
+ strokeLinecap: "round",
695
+ strokeLinejoin: "round",
696
+ strokeWidth: 2,
697
+ ...props,
698
+ children: [/* @__PURE__ */ jsx("circle", {
699
+ cx: 12,
700
+ cy: 12,
701
+ r: 10
702
+ }), /* @__PURE__ */ jsx("path", { d: "M16 16s-1.5-2-4-2-4 2-4 2M7.5 8 10 9M14 9l2.5-1M9 10h.01M15 10h.01" })]
703
+ });
704
+
705
+ //#endregion
706
+ //#region src/components/svgr/EmojiBeaming.tsx
707
+ const EmojiBeaming = (props) => /* @__PURE__ */ jsxs("svg", {
708
+ xmlns: "http://www.w3.org/2000/svg",
709
+ width: 40,
710
+ height: 40,
711
+ viewBox: "0 0 24 24",
712
+ fill: "none",
713
+ stroke: "currentColor",
714
+ strokeLinecap: "round",
715
+ strokeLinejoin: "round",
716
+ strokeWidth: 2,
717
+ ...props,
718
+ children: [/* @__PURE__ */ jsx("circle", {
719
+ cx: 12,
720
+ cy: 12,
721
+ r: 10
722
+ }), /* @__PURE__ */ jsx("path", { d: "M18 13a6 6 0 0 1-6 5 6 6 0 0 1-6-5h12ZM9 9h.01M15 9h.01" })]
723
+ });
724
+
725
+ //#endregion
726
+ //#region src/components/svgr/EmojiFrown.tsx
727
+ const EmojiFrown = (props) => /* @__PURE__ */ jsxs("svg", {
728
+ xmlns: "http://www.w3.org/2000/svg",
729
+ width: 40,
730
+ height: 40,
731
+ viewBox: "0 0 24 24",
732
+ fill: "none",
733
+ stroke: "currentColor",
734
+ strokeLinecap: "round",
735
+ strokeLinejoin: "round",
736
+ strokeWidth: 2,
737
+ ...props,
738
+ children: [/* @__PURE__ */ jsx("circle", {
739
+ cx: 12,
740
+ cy: 12,
741
+ r: 10
742
+ }), /* @__PURE__ */ jsx("path", { d: "M16 16s-1.5-2-4-2-4 2-4 2M9 9h.01M15 9h.01" })]
743
+ });
744
+
745
+ //#endregion
746
+ //#region src/components/svgr/EmojiMeh.tsx
747
+ const EmojiMeh = (props) => /* @__PURE__ */ jsxs("svg", {
748
+ xmlns: "http://www.w3.org/2000/svg",
749
+ width: 40,
750
+ height: 40,
751
+ viewBox: "0 0 24 24",
752
+ fill: "none",
753
+ stroke: "currentColor",
754
+ strokeLinecap: "round",
755
+ strokeLinejoin: "round",
756
+ strokeWidth: 2,
757
+ ...props,
758
+ children: [/* @__PURE__ */ jsx("circle", {
759
+ cx: 12,
760
+ cy: 12,
761
+ r: 10
762
+ }), /* @__PURE__ */ jsx("path", { d: "M8 15h8M9 9h.01M15 9h.01" })]
763
+ });
764
+
765
+ //#endregion
766
+ //#region src/components/svgr/EmojiSmile.tsx
767
+ const EmojiSmile = (props) => /* @__PURE__ */ jsxs("svg", {
768
+ xmlns: "http://www.w3.org/2000/svg",
769
+ width: 40,
770
+ height: 40,
771
+ viewBox: "0 0 24 24",
772
+ fill: "none",
773
+ stroke: "currentColor",
774
+ strokeLinecap: "round",
775
+ strokeLinejoin: "round",
776
+ strokeWidth: 2,
777
+ ...props,
778
+ children: [/* @__PURE__ */ jsx("circle", {
779
+ cx: 12,
780
+ cy: 12,
781
+ r: 10
782
+ }), /* @__PURE__ */ jsx("path", { d: "M8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01" })]
783
+ });
784
+
785
+ //#endregion
786
+ //#region src/components/svgr/Star.tsx
787
+ const Star = (props) => /* @__PURE__ */ jsx("svg", {
788
+ xmlns: "http://www.w3.org/2000/svg",
789
+ viewBox: "0 0 24 24",
790
+ width: 40,
791
+ height: 40,
792
+ fill: "none",
793
+ ...props,
794
+ children: /* @__PURE__ */ jsx("path", {
795
+ fill: "currentColor",
796
+ d: "m6.516 14.323-1.49 6.452a.998.998 0 0 0 1.529 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082a1 1 0 0 0-.59-1.74l-5.701-.454-2.467-5.461a.998.998 0 0 0-1.822 0L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.214 4.107zm2.853-4.326a.998.998 0 0 0 .832-.586L12 5.43l1.799 3.981a.998.998 0 0 0 .832.586l3.972.315-3.271 2.944c-.284.256-.397.65-.293 1.018l1.253 4.385-3.736-2.491a.995.995 0 0 0-1.109 0l-3.904 2.603 1.05-4.546a1 1 0 0 0-.276-.94l-3.038-2.962 4.09-.326z"
797
+ })
798
+ });
799
+
800
+ //#endregion
801
+ //#region src/components/svgr/StarSolid.tsx
802
+ const StarSolid = (props) => {
803
+ return /* @__PURE__ */ jsx("svg", {
804
+ xmlns: "http://www.w3.org/2000/svg",
805
+ width: 40,
806
+ height: 40,
807
+ viewBox: "0 0 24 24",
808
+ ...props,
809
+ children: /* @__PURE__ */ jsx("path", {
810
+ fill: "currentColor",
811
+ d: "M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z"
812
+ })
813
+ });
814
+ };
815
+
816
+ //#endregion
817
+ //#region src/components/questions/Rating.tsx
818
+ const StarRatingDisplay = ({ state, properties, classNames }) => {
819
+ const selectedValue = state.selectedValue ? Number(state.selectedValue) : null;
820
+ return /* @__PURE__ */ jsx("div", {
821
+ className: clsx("cf-rating-display-star", classNames?.ratingDisplayStar),
822
+ children: Array.from({ length: properties.scale }, (_, index) => {
823
+ const value = index + 1;
824
+ const radioValue = String(value);
825
+ const isFilled = selectedValue && value <= selectedValue;
826
+ return /* @__PURE__ */ jsx(Radio, {
827
+ className: clsx("cf-rating-star", classNames?.ratingStar),
828
+ value: radioValue,
829
+ "data-filled": isFilled ? "true" : void 0,
830
+ children: isFilled ? /* @__PURE__ */ jsx(StarSolid, {}) : /* @__PURE__ */ jsx(Star, {})
831
+ }, index);
832
+ })
833
+ });
834
+ };
835
+ const NumberRatingDisplay = ({ properties, classNames }) => {
836
+ return /* @__PURE__ */ jsx("div", {
837
+ className: clsx("cf-rating-display-number", classNames?.ratingDisplayNumber),
838
+ children: Array.from({ length: properties.scale }, (_, index) => {
839
+ const value = index + 1;
840
+ const radioValue = String(value);
841
+ return /* @__PURE__ */ jsx(Radio, {
842
+ className: clsx("cf-rating-number", classNames?.ratingNumber),
843
+ value: radioValue,
844
+ children: value
845
+ }, index);
846
+ })
847
+ });
848
+ };
849
+ const EmojiRatingDisplay = ({ properties, classNames, state }) => {
850
+ const EMOJI_FIVE = useMemo(() => [
851
+ EmojiAngry,
852
+ EmojiFrown,
853
+ EmojiMeh,
854
+ EmojiSmile,
855
+ EmojiBeaming
856
+ ], []);
857
+ const EMOJI_THREE = useMemo(() => [
858
+ EmojiFrown,
859
+ EmojiMeh,
860
+ EmojiSmile
861
+ ], []);
862
+ if (!useMemo(() => {
863
+ return properties.scale === 3 || properties.scale === 5;
864
+ }, [properties.scale])) return /* @__PURE__ */ jsx(NumberRatingDisplay, {
865
+ properties,
866
+ classNames,
867
+ state
868
+ });
869
+ return /* @__PURE__ */ jsx("div", {
870
+ className: clsx("cf-rating-display-emoji", classNames?.ratingDisplayEmoji),
871
+ children: Array.from({ length: properties.scale }, (_, index) => {
872
+ const value = index + 1;
873
+ const radioValue = String(value);
874
+ const Emoji = properties.scale === 5 ? EMOJI_FIVE[index] : EMOJI_THREE[index];
875
+ return /* @__PURE__ */ jsx(Radio, {
876
+ className: clsx("cf-rating-emoji", classNames?.ratingEmoji),
877
+ value: radioValue,
878
+ children: Emoji && /* @__PURE__ */ jsx(Emoji, {})
879
+ }, index);
880
+ })
881
+ });
882
+ };
883
+ const RatingDisplay = (props) => {
884
+ switch (props.properties.display ?? "star") {
885
+ case "emoji": return /* @__PURE__ */ jsx(EmojiRatingDisplay, { ...props });
886
+ case "number": return /* @__PURE__ */ jsx(NumberRatingDisplay, { ...props });
887
+ case "star":
888
+ default: return /* @__PURE__ */ jsx(StarRatingDisplay, { ...props });
889
+ }
890
+ };
891
+ const Rating = ({ properties, title, description, classNames, onChange, id, error, required }) => {
892
+ const [selected, setSelected] = useState(null);
893
+ const submissionValue = useMemo(() => {
894
+ return selected ? Number(selected) : void 0;
895
+ }, [selected]);
896
+ useEffect(() => {
897
+ onChange(submissionValue);
898
+ }, [onChange, submissionValue]);
899
+ return /* @__PURE__ */ jsx(RadioGroup, {
900
+ isRequired: required,
901
+ isInvalid: !!error,
902
+ name: id,
903
+ className: clsx("cf-question", classNames?.question),
904
+ value: selected,
905
+ onChange: setSelected,
906
+ children: ({ state }) => /* @__PURE__ */ jsxs(Fragment, { children: [
907
+ /* @__PURE__ */ jsxs("div", {
908
+ className: clsx("cf-header", classNames?.header),
909
+ children: [/* @__PURE__ */ jsx(Label, {
910
+ className: clsx("cf-title", classNames?.title),
911
+ children: title
912
+ }), description && /* @__PURE__ */ jsx(Text, {
913
+ slot: "description",
914
+ className: clsx("cf-description", classNames?.description),
915
+ children: description
916
+ })]
917
+ }),
918
+ /* @__PURE__ */ jsxs("div", {
919
+ className: clsx("cf-rating-section", classNames?.ratingSection),
920
+ children: [/* @__PURE__ */ jsx(RatingDisplay, {
921
+ state,
922
+ properties,
923
+ classNames
924
+ }), /* @__PURE__ */ jsxs("div", {
925
+ className: clsx("cf-rating-text", classNames?.ratingText),
926
+ children: [/* @__PURE__ */ jsx("span", {
927
+ className: clsx("cf-rating-text-lower", classNames?.ratingTextLower),
928
+ children: properties.labels?.lower
929
+ }), /* @__PURE__ */ jsx("span", {
930
+ className: clsx("cf-rating-text-upper", classNames?.ratingTextUpper),
931
+ children: properties.labels?.upper
932
+ })]
933
+ })]
934
+ }),
935
+ /* @__PURE__ */ jsx(FieldError, {
936
+ className: clsx("cf-error", classNames?.error),
937
+ children: error
938
+ })
939
+ ] })
940
+ });
941
+ };
942
+
943
+ //#endregion
944
+ //#region src/components/questions/SingleChoice.tsx
945
+ const SingleChoiceRadio = ({ classNames, className, ...props }) => {
946
+ return /* @__PURE__ */ jsx(Radio, {
947
+ ...props,
948
+ className: clsx(className, classNames?.radio),
949
+ children: composeRenderProps(props.children, (children) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", { className: clsx("cf-radio-indicator", classNames?.indicator) }), children] }))
950
+ });
951
+ };
952
+ const SingleChoice = ({ title, description, properties, classNames, onChange, id, required, error }) => {
953
+ const [value, setValue] = useState(null);
954
+ const [othersValue, setOthersValue] = useState("");
955
+ const hasEditable = !!properties.editable;
956
+ const othersRef = useRef(null);
957
+ const othersInputRef = useRef(null);
958
+ const submissionValue = useMemo(() => {
959
+ if (value === RADIO_OTHERS_VALUE) return othersValue || RADIO_OTHERS_VALUE;
960
+ return value ?? void 0;
961
+ }, [othersValue, value]);
962
+ useEffect(() => {
963
+ onChange(submissionValue);
964
+ }, [onChange, submissionValue]);
965
+ return /* @__PURE__ */ jsx(RadioGroup, {
966
+ isRequired: required,
967
+ isInvalid: !!error,
968
+ name: id,
969
+ onChange: (key) => {
970
+ setValue(key);
971
+ if (key === RADIO_OTHERS_VALUE) requestAnimationFrame(() => {
972
+ if (!othersInputRef.current) return;
973
+ othersInputRef.current.focus();
974
+ });
975
+ },
976
+ value,
977
+ className: clsx("cf-question", classNames?.question),
978
+ children: ({ state }) => /* @__PURE__ */ jsxs(Fragment, { children: [
979
+ /* @__PURE__ */ jsxs("div", {
980
+ className: clsx("cf-header", classNames?.header),
981
+ children: [/* @__PURE__ */ jsx(Label, {
982
+ className: clsx("cf-title", classNames?.title),
983
+ children: title
984
+ }), description && /* @__PURE__ */ jsx(Text, {
985
+ slot: "description",
986
+ className: clsx("cf-description", classNames?.description),
987
+ children: description
988
+ })]
989
+ }),
990
+ /* @__PURE__ */ jsxs("div", {
991
+ className: clsx("cf-response-choices", classNames?.group),
992
+ children: [properties.options.map((option, index) => /* @__PURE__ */ jsx(SingleChoiceRadio, {
993
+ classNames: {
994
+ indicator: classNames?.indicator,
995
+ radio: clsx("cf-radio", classNames?.radio)
996
+ },
997
+ value: option.value,
998
+ children: option.label
999
+ }, index)), hasEditable && /* @__PURE__ */ jsxs("div", {
1000
+ className: clsx("cf-others-choice", classNames?.othersChoice),
1001
+ children: [/* @__PURE__ */ jsx(SingleChoiceRadio, {
1002
+ inputRef: othersRef,
1003
+ value: RADIO_OTHERS_VALUE,
1004
+ classNames: {
1005
+ indicator: classNames?.indicator,
1006
+ radio: clsx("cf-radio", classNames?.radio)
1007
+ },
1008
+ children: properties.editable?.label
1009
+ }), /* @__PURE__ */ jsx(Input, {
1010
+ tabIndex: state.selectedValue === RADIO_OTHERS_VALUE ? 0 : -1,
1011
+ ref: othersInputRef,
1012
+ value: othersValue,
1013
+ onChange: (e) => setOthersValue(e.target.value),
1014
+ onFocus: () => {
1015
+ if (othersRef.current?.checked === false) othersRef.current.click();
1016
+ },
1017
+ className: clsx("cf-input", classNames?.othersInput)
1018
+ })]
1019
+ })]
1020
+ }),
1021
+ /* @__PURE__ */ jsx(FieldError, {
1022
+ className: clsx("cf-error", classNames?.error),
1023
+ children: error
1024
+ })
1025
+ ] })
1026
+ });
1027
+ };
1028
+
1029
+ //#endregion
1030
+ //#region src/components/questions/factory.tsx
1031
+ const SurveyQuestionFactory = ({ question, onChange, error }) => {
1032
+ switch (question.properties.type) {
1033
+ case "rating": return /* @__PURE__ */ jsx(Rating, {
1034
+ ...question,
1035
+ properties: question.properties,
1036
+ onChange,
1037
+ error
1038
+ }, question.id);
1039
+ case "open-ended": return /* @__PURE__ */ jsx(FreeText, {
1040
+ ...question,
1041
+ properties: question.properties,
1042
+ onChange,
1043
+ error
1044
+ }, question.id);
1045
+ case "single-select": return /* @__PURE__ */ jsx(SingleChoice, {
1046
+ ...question,
1047
+ properties: question.properties,
1048
+ onChange,
1049
+ error
1050
+ }, question.id);
1051
+ case "multi-select": return /* @__PURE__ */ jsx(MultiChoice, {
1052
+ ...question,
1053
+ properties: question.properties,
1054
+ onChange,
1055
+ error
1056
+ }, question.id);
1057
+ }
1058
+ return /* @__PURE__ */ jsx("div", { children: "Unsupported question type" }, question.id);
1059
+ };
1060
+
1061
+ //#endregion
1062
+ //#region src/components/SubmitButton.tsx
1063
+ const SubmitButton = ({ onPress, isDisabled, children = "Submit" }) => {
1064
+ return /* @__PURE__ */ jsx(Button, {
1065
+ isDisabled,
1066
+ className: "cf-submit-button",
1067
+ onPress,
1068
+ children
1069
+ });
1070
+ };
1071
+
1072
+ //#endregion
1073
+ //#region src/components/presentation/Flow.tsx
1074
+ const getInitialState = (type) => {
1075
+ switch (type) {
1076
+ case "multi-select": return [];
1077
+ default: return "";
1078
+ }
1079
+ };
1080
+ const Flow = ({ onSubmit, children, question }) => {
1081
+ const [answer, setAnswer] = useState(getInitialState(question.properties.type));
1082
+ const validator = useMemo(() => getAnswerValidator(question.properties, { required: question.required }), [question.properties, question.required]);
1083
+ const error = useMemo(() => {
1084
+ const parseResult = validator.safeParse(answer);
1085
+ if (parseResult.success) return;
1086
+ return prettifyError(parseResult.error);
1087
+ }, [answer, validator]);
1088
+ return /* @__PURE__ */ jsxs(Fragment, { children: [children({
1089
+ onChange: setAnswer,
1090
+ question,
1091
+ error
1092
+ }), /* @__PURE__ */ jsx(SubmitButton, {
1093
+ isDisabled: !!error,
1094
+ onPress: () => onSubmit(answer)
1095
+ })] });
1096
+ };
1097
+
1098
+ //#endregion
1099
+ //#region src/components/presentation/Embed.tsx
1100
+ const Embed = ({ isCompleted, onSubmit, children }) => {
1101
+ return /* @__PURE__ */ jsxs(Fragment, { children: [children, isCompleted && /* @__PURE__ */ jsx(SubmitButton, { onPress: onSubmit })] });
1102
+ };
1103
+
1104
+ //#endregion
1105
+ //#region src/templates/EmbeddedConfetti.tsx
1106
+ const EmbeddedConfetti = (props) => {
1107
+ return /* @__PURE__ */ jsx(Confetti, {
1108
+ ...props,
1109
+ shouldAutoSubmit: false,
1110
+ children: ({ questions, update, isCompleted, submit, isSubmitted }) => {
1111
+ if (isSubmitted) return /* @__PURE__ */ jsx(ThankYouMessage, {});
1112
+ return /* @__PURE__ */ jsx(Embed, {
1113
+ onSubmit: submit,
1114
+ isCompleted,
1115
+ children: questions.filter(({ visible }) => !!visible).map((question) => {
1116
+ return /* @__PURE__ */ jsx(SurveyQuestionFactory, {
1117
+ question,
1118
+ onChange: (answer) => answer && update({
1119
+ question: question.position,
1120
+ answer
1121
+ })
1122
+ }, question.position);
1123
+ })
1124
+ });
1125
+ }
1126
+ });
1127
+ };
1128
+
1129
+ //#endregion
1130
+ //#region src/templates/StepperConfetti.tsx
1131
+ const StepperConfetti = (props) => {
1132
+ return /* @__PURE__ */ jsx(Confetti, {
1133
+ ...props,
1134
+ shouldAutoSubmit: true,
1135
+ children: ({ current, questions, update, isSubmitted }) => {
1136
+ if (isSubmitted) return /* @__PURE__ */ jsx(ThankYouMessage, {});
1137
+ const currentQuestion = questions[current];
1138
+ if (!currentQuestion) return /* @__PURE__ */ jsx(Fragment, {});
1139
+ return /* @__PURE__ */ jsx(Flow, {
1140
+ question: currentQuestion,
1141
+ onSubmit: (answer) => {
1142
+ return update({
1143
+ question: current,
1144
+ answer
1145
+ });
1146
+ },
1147
+ children: (flowProps) => /* @__PURE__ */ jsx(SurveyQuestionFactory, { ...flowProps })
1148
+ });
1149
+ }
1150
+ });
1151
+ };
1152
+
1153
+ //#endregion
1154
+ export { Confetti, ConfettiContext, ConfettiController, ConfettiProvider, Embed, EmbeddedConfetti, Flow, FreeText, MultiChoice, PopoverContainer, Rating, SingleChoice, StepperConfetti, SurveyQuestionFactory, ThankYouMessage, useConfetti };
1155
+ //# sourceMappingURL=index.js.map