fixes feedback

This commit is contained in:
pandeymangg
2025-11-20 17:02:12 +05:30
parent b1a7b929bd
commit 22ad78a187
16 changed files with 84 additions and 116 deletions

View File

@@ -18,6 +18,7 @@ import {
TSurveyAddressElement,
TSurveyContactInfoElement,
TSurveyElement,
TSurveyElementChoice,
TSurveyElementTypeEnum,
} from "@formbricks/types/surveys/elements";
import {
@@ -33,7 +34,6 @@ import {
TSurveyElementSummaryRanking,
TSurveyElementSummaryRating,
TSurveyLanguage,
TSurveyQuestionChoice,
TSurveySummary,
} from "@formbricks/types/surveys/types";
import { getTextContent } from "@formbricks/types/surveys/validation";
@@ -323,7 +323,7 @@ const checkForI18n = (
// Return the localized value of the choice fo multiSelect single question
if (question && "choices" in question) {
const choice = question.choices?.find(
(choice: TSurveyQuestionChoice) => choice.label?.[languageCode] === responseData[id]
(choice: TSurveyElementChoice) => choice.label?.[languageCode] === responseData[id]
);
return choice && "label" in choice
? getLocalizedValue(choice.label, "default") || responseData[id]

View File

@@ -6,13 +6,12 @@ import { ImagePlusIcon, TrashIcon } from "lucide-react";
import { useCallback, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { type TI18nString } from "@formbricks/types/i18n";
import { TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import {
TSurvey,
TSurveyEndScreenCard,
TSurveyQuestionChoice,
TSurveyRedirectUrlCard,
} from "@formbricks/types/surveys/types";
TSurveyElement,
TSurveyElementChoice,
TSurveyElementTypeEnum,
} from "@formbricks/types/surveys/elements";
import { TSurvey, TSurveyEndScreenCard, TSurveyRedirectUrlCard } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { useSyncScroll } from "@/lib/utils/hooks/useSyncScroll";
@@ -44,7 +43,7 @@ interface QuestionFormInputProps {
questionIdx: number;
updateQuestion?: (questionIdx: number, data: Partial<TSurveyElement>) => void;
updateSurvey?: (data: Partial<TSurveyEndScreenCard> | Partial<TSurveyRedirectUrlCard>) => void;
updateChoice?: (choiceIdx: number, data: Partial<TSurveyQuestionChoice>) => void;
updateChoice?: (choiceIdx: number, data: Partial<TSurveyElementChoice>) => void;
updateMatrixLabel?: (index: number, type: "row" | "column", matrixLabel: TI18nString) => void;
isInvalid: boolean;
selectedLanguageCode: string;

View File

@@ -1,7 +1,7 @@
"use client";
import { createId } from "@paralleldrive/cuid2";
import { Project } from "@prisma/client";
import { type Project } from "@prisma/client";
import { PlusIcon } from "lucide-react";
import { useState } from "react";
import toast from "react-hot-toast";

View File

@@ -5,8 +5,12 @@ import { CSS } from "@dnd-kit/utilities";
import { GripVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { TI18nString } from "@formbricks/types/i18n";
import { TSurveyMultipleChoiceElement, TSurveyRankingElement } from "@formbricks/types/surveys/elements";
import { TSurvey, TSurveyLanguage, TSurveyQuestionChoice } from "@formbricks/types/surveys/types";
import {
TSurveyElementChoice,
TSurveyMultipleChoiceElement,
TSurveyRankingElement,
} from "@formbricks/types/surveys/elements";
import { TSurvey, TSurveyLanguage } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { cn } from "@/lib/cn";
import { createI18nString } from "@/lib/i18n/utils";
@@ -16,7 +20,7 @@ import { TooltipRenderer } from "@/modules/ui/components/tooltip";
import { isLabelValidForAllLanguages } from "../lib/validation";
interface ChoiceProps {
choice: TSurveyQuestionChoice;
choice: TSurveyElementChoice;
choiceIdx: number;
questionIdx: number;
updateChoice: (choiceIdx: number, updatedAttributes: { label: TI18nString }) => void;

View File

@@ -12,7 +12,7 @@ import { VerifyEmail } from "@/modules/survey/link/components/verify-email";
import { getPrefillValue } from "@/modules/survey/link/lib/utils";
import { SurveyInline } from "@/modules/ui/components/survey";
let setQuestionId = (_: string) => {};
let setBlockId = (_: string) => {};
let setResponseData = (_: TResponseData) => {};
interface LinkSurveyProps {
@@ -158,7 +158,11 @@ export const LinkSurvey = ({
};
const handleResetSurvey = () => {
setQuestionId(survey.welcomeCard.enabled ? "start" : questions[0].id);
if (survey.welcomeCard.enabled) {
setBlockId("start");
} else if (survey.blocks[0]) {
setBlockId(survey.blocks[0].id);
}
setResponseData({});
};
@@ -191,8 +195,8 @@ export const LinkSurvey = ({
prefillResponseData={prefillValue}
skipPrefilled={skipPrefilled}
responseCount={responseCount}
getSetQuestionId={(f: (value: string) => void) => {
setQuestionId = f;
getSetBlockId={(f: (value: string) => void) => {
setBlockId = f;
}}
getSetResponseData={(f: (value: TResponseData) => void) => {
setResponseData = f;

View File

@@ -8,7 +8,6 @@ export interface SurveyBaseProps {
isBrandingEnabled: boolean;
getSetIsError?: (getSetError: (value: boolean) => void) => void;
getSetIsResponseSendingFinished?: (getSetIsResponseSendingFinished: (value: boolean) => void) => void;
getSetQuestionId?: (getSetQuestionId: (value: string) => void) => void;
getSetResponseData?: (getSetResponseData: (value: TResponseData) => void) => void;
onDisplay?: () => void;
onResponse?: (response: TResponseUpdate) => void;

View File

@@ -12,7 +12,6 @@ import { getLocalizedValue } from "@/lib/i18n";
import { cn } from "@/lib/utils";
interface BlockConditionalProps {
// survey: TJsEnvironmentStateSurvey;
block: TSurveyBlock;
value: TResponseData;
onChange: (responseData: TResponseData) => void;
@@ -36,7 +35,6 @@ interface BlockConditionalProps {
}
export function BlockConditional({
// survey,
block,
value,
onChange,

View File

@@ -2,8 +2,11 @@ import { useEffect, useRef } from "preact/hooks";
import { type TJsFileUploadParams } from "@formbricks/types/js";
import { type TResponseData, type TResponseDataValue, type TResponseTtc } from "@formbricks/types/responses";
import { type TUploadFileConfig } from "@formbricks/types/storage";
import { TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import { type TSurveyQuestionChoice } from "@formbricks/types/surveys/types";
import {
TSurveyElement,
TSurveyElementChoice,
TSurveyElementTypeEnum,
} from "@formbricks/types/surveys/elements";
import { AddressQuestion } from "@/components/questions/address-question";
import { CalQuestion } from "@/components/questions/cal-question";
import { ConsentQuestion } from "@/components/questions/consent-question";
@@ -74,10 +77,7 @@ export function ElementConditional({
}
}, [formRef]);
const getResponseValueForRankingQuestion = (
value: string[],
choices: TSurveyQuestionChoice[]
): string[] => {
const getResponseValueForRankingQuestion = (value: string[], choices: TSurveyElementChoice[]): string[] => {
return value
.map((entry) => {
// First check if entry is already a valid choice ID
@@ -87,7 +87,7 @@ export function ElementConditional({
// Otherwise, treat it as a localized label and find the choice by label
return choices.find((choice) => getLocalizedValue(choice.label, languageCode) === entry)?.id;
})
.filter((id): id is TSurveyQuestionChoice["id"] => id !== undefined);
.filter((id): id is TSurveyElementChoice["id"] => id !== undefined);
};
useEffect(() => {
@@ -99,6 +99,20 @@ export function ElementConditional({
// eslint-disable-next-line react-hooks/exhaustive-deps -- we want to run this only once when the element renders for the first time
}, []);
const isRecognizedType = Object.values(TSurveyElementTypeEnum).includes(element.type);
useEffect(() => {
if (!isRecognizedType) {
console.warn(
`[Formbricks] Unrecognized element type "${element.type}" for element with id "${element.id}". No component will be rendered.`
);
}
}, [element.type, element.id, isRecognizedType]);
if (!isRecognizedType) {
return null;
}
return (
<div ref={containerRef}>
{element.type === TSurveyElementTypeEnum.OpenText ? (

View File

@@ -26,7 +26,7 @@ import { evaluateLogic, performActions } from "@/lib/logic";
import { parseRecallInformation } from "@/lib/recall";
import { ResponseQueue } from "@/lib/response-queue";
import { SurveyState } from "@/lib/survey-state";
import { cn, findBlockByElementId, getDefaultLanguageCode, getElementsFromSurvey } from "@/lib/utils";
import { cn, findBlockByElementId, getDefaultLanguageCode, getElementsFromSurveyBlocks } from "@/lib/utils";
import { TResponseErrorCodesEnum } from "@/types/response-error-codes";
interface VariableStackEntry {
@@ -58,7 +58,6 @@ export function Survey({
languageCode,
getSetIsError,
getSetIsResponseSendingFinished,
getSetQuestionId,
getSetBlockId,
getSetResponseData,
responseCount,
@@ -140,7 +139,7 @@ export function Survey({
return null;
}, [appUrl, environmentId, getSetIsError, getSetIsResponseSendingFinished, surveyState]);
const questions = useMemo(() => getElementsFromSurvey(localSurvey), [localSurvey]);
const questions = useMemo(() => getElementsFromSurveyBlocks(localSurvey.blocks), [localSurvey.blocks]);
const originalQuestionRequiredStates = useMemo(() => {
return questions.reduce<Record<string, boolean>>((acc, question) => {
@@ -173,7 +172,7 @@ export function Survey({
const [blockId, setBlockId] = useState(() => {
if (startAtQuestionId) {
// If starting at a specific question, find its parent block
const startBlock = findBlockByElementId(localSurvey, startAtQuestionId);
const startBlock = findBlockByElementId(localSurvey.blocks, startAtQuestionId);
return startBlock?.id || localSurvey.blocks[0]?.id;
} else if (localSurvey.welcomeCard.enabled) {
return "start";
@@ -312,18 +311,6 @@ export function Survey({
}
}, [getSetIsError]);
useEffect(() => {
if (getSetQuestionId) {
getSetQuestionId((value: string) => {
// Convert question ID to block ID
const block = findBlockByElementId(survey, value);
if (block) {
setBlockId(block.id);
}
});
}
}, [getSetQuestionId, survey]);
useEffect(() => {
if (getSetBlockId) {
getSetBlockId((value: string) => {
@@ -770,7 +757,6 @@ export function Survey({
Boolean(block) && (
<BlockConditional
key={block.id}
// survey={localSurvey}
surveyId={localSurvey.id}
block={{
...block,

View File

@@ -7,7 +7,7 @@ import { SubmitButton } from "@/components/buttons/submit-button";
import { ScrollableContainer } from "@/components/wrappers/scrollable-container";
import { getLocalizedValue } from "@/lib/i18n";
import { replaceRecallInfo } from "@/lib/recall";
import { calculateElementIdx, getElementsFromSurvey } from "@/lib/utils";
import { calculateElementIdx, getElementsFromSurveyBlocks } from "@/lib/utils";
import { Headline } from "./headline";
import { Subheader } from "./subheader";
@@ -84,7 +84,7 @@ export function WelcomeCard({
const { t } = useTranslation();
const calculateTimeToComplete = () => {
const questions = getElementsFromSurvey(survey);
const questions = getElementsFromSurveyBlocks(survey.blocks);
let totalCards = questions.length;
if (survey.endings.length > 0) totalCards += 1;
let idx = calculateElementIdx(survey, 0, totalCards);

View File

@@ -2,8 +2,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useCallback, useMemo, useState } from "preact/hooks";
import { useTranslation } from "react-i18next";
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
import type { TSurveyRankingElement } from "@formbricks/types/surveys/elements";
import type { TSurveyQuestionChoice } from "@formbricks/types/surveys/types";
import type { TSurveyElementChoice, TSurveyRankingElement } from "@formbricks/types/surveys/elements";
import { Headline } from "@/components/general/headline";
import { QuestionMedia } from "@/components/general/question-media";
import { Subheader } from "@/components/general/subheader";
@@ -55,7 +54,7 @@ export function RankingQuestion({
const sortedItems = useMemo(() => {
return localValue
.map((id) => question.choices.find((c) => c.id === id))
.filter((item): item is TSurveyQuestionChoice => item !== undefined);
.filter((item): item is TSurveyElementChoice => item !== undefined);
}, [localValue, question.choices]);
const unsortedItems = useMemo(() => {
@@ -66,7 +65,7 @@ export function RankingQuestion({
}, [question.choices, question.shuffleOption, localValue, sortedItems, shuffledChoicesIds]);
const handleItemClick = useCallback(
(item: TSurveyQuestionChoice) => {
(item: TSurveyElementChoice) => {
const isAlreadySorted = localValue.includes(item.id);
const newLocalValue = isAlreadySorted
? localValue.filter((id) => id !== item.id)
@@ -77,7 +76,7 @@ export function RankingQuestion({
// Immediately update parent state with the new ranking
const sortedLabels = newLocalValue
.map((id) => question.choices.find((c) => c.id === id))
.filter((item): item is TSurveyQuestionChoice => item !== undefined)
.filter((item): item is TSurveyElementChoice => item !== undefined)
.map((item) => getLocalizedValue(item.label, languageCode));
onChange({ [question.id]: sortedLabels });
@@ -101,7 +100,7 @@ export function RankingQuestion({
// Immediately update parent state with the new ranking
const sortedLabels = newLocalValue
.map((id) => question.choices.find((c) => c.id === id))
.filter((item): item is TSurveyQuestionChoice => item !== undefined)
.filter((item): item is TSurveyElementChoice => item !== undefined)
.map((item) => getLocalizedValue(item.label, languageCode));
onChange({ [question.id]: sortedLabels });

View File

@@ -5,7 +5,7 @@ import { TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/survey
import { TConditionGroup, TSingleCondition } from "@formbricks/types/surveys/logic";
import { TSurveyVariable } from "@formbricks/types/surveys/types";
import { getLocalizedValue } from "@/lib/i18n";
import { getElementsFromSurvey } from "./utils";
import { getElementsFromSurveyBlocks } from "./utils";
const getVariableValue = (
variables: TSurveyVariable[],
@@ -89,7 +89,7 @@ const getLeftOperandValue = (
) => {
switch (leftOperand.type) {
case "question":
const questions = getElementsFromSurvey(localSurvey);
const questions = getElementsFromSurveyBlocks(localSurvey.blocks);
const currentQuestion = questions.find((q) => q.id === leftOperand.value);
if (!currentQuestion) return undefined;
@@ -223,7 +223,7 @@ const evaluateSingleCondition = (
let leftField: TSurveyElement | TSurveyVariable | string;
const questions = getElementsFromSurvey(localSurvey);
const questions = getElementsFromSurveyBlocks(localSurvey.blocks);
if (condition.leftOperand?.type === "question") {
leftField = questions.find((q) => q.id === condition.leftOperand?.value) ?? "";
} else if (condition.leftOperand?.type === "variable") {

View File

@@ -2,11 +2,11 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
import type { TJsEnvironmentStateSurvey } from "../../../types/js";
import { type TAllowedFileExtension, mimeTypes } from "../../../types/storage";
import { TSurveyElementTypeEnum } from "../../../types/surveys/elements";
import type { TSurveyLanguage, TSurveyQuestionChoice } from "../../../types/surveys/types";
import type { TSurveyLanguage } from "../../../types/surveys/types";
import {
findBlockByElementId,
getDefaultLanguageCode,
getElementsFromSurvey,
getElementsFromSurveyBlocks,
getMimeType,
getShuffledChoicesIds,
getShuffledRowIndices,
@@ -140,12 +140,12 @@ describe("getShuffledChoicesIds", () => {
mockGetRandomValues.mockReset();
});
const choicesBase: TSurveyQuestionChoice[] = [
const choicesBase = [
{ id: "c1", label: { en: "Choice 1" } },
{ id: "c2", label: { en: "Choice 2" } },
{ id: "c3", label: { en: "Choice 3" } },
];
const choicesWithOther: TSurveyQuestionChoice[] = [...choicesBase, { id: "other", label: { en: "Other" } }];
const choicesWithOther = [...choicesBase, { id: "other", label: { en: "Other" } }];
test('should return unshuffled for "none"', () => {
expect(getShuffledChoicesIds(choicesBase, "none")).toEqual(["c1", "c2", "c3"]);
@@ -225,7 +225,7 @@ describe("getQuestionsFromSurvey", () => {
],
};
const questions = getElementsFromSurvey(survey);
const questions = getElementsFromSurveyBlocks(survey.blocks);
expect(questions).toHaveLength(3);
expect(questions[0].id).toBe("q1");
expect(questions[1].id).toBe("q2");
@@ -238,7 +238,7 @@ describe("getQuestionsFromSurvey", () => {
blocks: [],
} as TJsEnvironmentStateSurvey;
expect(getElementsFromSurvey(survey)).toEqual([]);
expect(getElementsFromSurveyBlocks(survey.blocks)).toEqual([]);
});
test("should handle blocks with no elements", () => {
@@ -263,7 +263,7 @@ describe("getQuestionsFromSurvey", () => {
],
};
const questions = getElementsFromSurvey(survey);
const questions = getElementsFromSurveyBlocks(survey.blocks);
expect(questions).toHaveLength(1);
expect(questions[0].id).toBe("q1");
});
@@ -313,17 +313,17 @@ describe("findBlockByElementId", () => {
};
test("should find block containing the element", () => {
const block = findBlockByElementId(survey, "q1");
const block = findBlockByElementId(survey.blocks, "q1");
expect(block).toBeDefined();
expect(block?.id).toBe("block1");
const block2 = findBlockByElementId(survey, "q3");
const block2 = findBlockByElementId(survey.blocks, "q3");
expect(block2).toBeDefined();
expect(block2?.id).toBe("block2");
});
test("should return undefined for non-existent element", () => {
const block = findBlockByElementId(survey, "nonexistent");
const block = findBlockByElementId(survey.blocks, "nonexistent");
expect(block).toBeUndefined();
});
});

View File

@@ -2,9 +2,9 @@ import { type Result, err, ok, wrapThrowsAsync } from "@formbricks/types/error-h
import { type ApiErrorResponse } from "@formbricks/types/errors";
import { type TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TAllowedFileExtension, mimeTypes } from "@formbricks/types/storage";
import { TSurveyBlockLogic, TSurveyBlockLogicAction } from "@formbricks/types/surveys/blocks";
import { type TSurveyElement } from "@formbricks/types/surveys/elements";
import { type TShuffleOption, type TSurveyQuestionChoice } from "@formbricks/types/surveys/types";
import { TSurveyBlock, TSurveyBlockLogic, TSurveyBlockLogicAction } from "@formbricks/types/surveys/blocks";
import { type TSurveyElement, TSurveyElementChoice } from "@formbricks/types/surveys/elements";
import { type TShuffleOption } from "@formbricks/types/surveys/types";
import { ApiResponse, ApiSuccessResponse } from "@/types/api";
export const cn = (...classes: string[]) => {
@@ -41,7 +41,7 @@ export const getShuffledRowIndices = (n: number, shuffleOption: TShuffleOption):
};
export const getShuffledChoicesIds = (
choices: TSurveyQuestionChoice[],
choices: TSurveyElementChoice[],
shuffleOption: TShuffleOption
): string[] => {
const otherOption = choices.find((choice) => {
@@ -79,10 +79,10 @@ export const calculateElementIdx = (
currentQustionIdx: number,
totalCards: number
): number => {
const questions = getElementsFromSurvey(survey);
const questions = getElementsFromSurveyBlocks(survey.blocks);
const currentQuestion = questions[currentQustionIdx];
const middleIdx = Math.floor(totalCards / 2);
const possibleNextBlockIds = getPossibleNextBlocks(survey, currentQuestion);
const possibleNextBlockIds = getPossibleNextBlocks(survey.blocks, currentQuestion);
const endingCardIds = survey.endings.map((ending) => ending.id);
// Convert block IDs to element IDs (get first element of each block)
@@ -106,9 +106,9 @@ export const calculateElementIdx = (
return elementIdx;
};
const getPossibleNextBlocks = (survey: TJsEnvironmentStateSurvey, element: TSurveyElement): string[] => {
const getPossibleNextBlocks = (blocks: TSurveyBlock[], element: TSurveyElement): string[] => {
// In the blocks model, logic is stored at the block level
const parentBlock = findBlockByElementId(survey, element.id);
const parentBlock = findBlockByElementId(blocks, element.id);
if (!parentBlock?.logic) return [];
const possibleBlockIds: string[] = [];
@@ -197,7 +197,7 @@ export const checkIfSurveyIsRTL = (survey: TJsEnvironmentStateSurvey, languageCo
}
}
const questions = getElementsFromSurvey(survey);
const questions = getElementsFromSurveyBlocks(survey.blocks);
for (const question of questions) {
const questionHeadline = question.headline[languageCode];
@@ -212,11 +212,11 @@ export const checkIfSurveyIsRTL = (survey: TJsEnvironmentStateSurvey, languageCo
/**
* Derives a flat array of elements from the survey's blocks structure.
* @param survey The survey object with blocks
* @param blocks The blocks array
* @returns An array of TSurveyElement (pure elements without block-level properties)
*/
export const getElementsFromSurvey = (survey: TJsEnvironmentStateSurvey): TSurveyElement[] =>
survey.blocks.flatMap((block) => block.elements);
export const getElementsFromSurveyBlocks = (blocks: TSurveyBlock[]): TSurveyElement[] =>
blocks.flatMap((block) => block.elements);
/**
* Finds the parent block that contains the specified element ID.
@@ -225,8 +225,8 @@ export const getElementsFromSurvey = (survey: TJsEnvironmentStateSurvey): TSurve
* @param elementId The ID of the element to find
* @returns The parent block or undefined if not found
*/
export const findBlockByElementId = (survey: TJsEnvironmentStateSurvey, elementId: string) =>
survey.blocks.find((b) => b.elements.some((e) => e.id === elementId));
export const findBlockByElementId = (blocks: TSurveyBlock[], elementId: string) =>
blocks.find((block) => block.elements.some((e) => e.id === elementId));
/**
* Converts a block ID to the first element ID in that block.
@@ -242,39 +242,3 @@ export const getFirstElementIdInBlock = (
const block = survey.blocks.find((b) => b.id === blockId);
return block?.elements[0]?.id;
};
/**
* Gets a block by its ID.
* @param survey The survey object
* @param blockId The block ID to find
* @returns The block or undefined if not found
*/
export const getBlockById = (survey: TJsEnvironmentStateSurvey, blockId: string) => {
return survey.blocks.find((b) => b.id === blockId);
};
/**
* Gets the next block ID after the current block.
* @param survey The survey object
* @param currentBlockId The current block ID
* @returns The next block ID or undefined if current block is last
*/
export const getNextBlockId = (
survey: TJsEnvironmentStateSurvey,
currentBlockId: string
): string | undefined => {
const currentIndex = survey.blocks.findIndex((b) => b.id === currentBlockId);
if (currentIndex === -1) return undefined;
return survey.blocks[currentIndex + 1]?.id;
};
/**
* Gets all element IDs in a block.
* @param survey The survey object
* @param blockId The block ID
* @returns Array of element IDs in the block
*/
export const getElementIdsInBlock = (survey: TJsEnvironmentStateSurvey, blockId: string): string[] => {
const block = getBlockById(survey, blockId);
return block?.elements.map((e) => e.id) ?? [];
};

View File

@@ -10,7 +10,6 @@ export interface SurveyBaseProps {
isBrandingEnabled: boolean;
getSetIsError?: (getSetError: (value: boolean) => void) => void;
getSetIsResponseSendingFinished?: (getSetIsResponseSendingFinished: (value: boolean) => void) => void;
getSetQuestionId?: (getSetQuestionId: (value: string) => void) => void;
getSetBlockId?: (getSetBlockId: (value: string) => void) => void;
getSetResponseData?: (getSetResponseData: (value: TResponseData) => void) => void;
onDisplay?: () => Promise<void>;

View File

@@ -126,6 +126,8 @@ export const ZSurveyElementChoice = z.object({
label: ZI18nString,
});
export type TSurveyElementChoice = z.infer<typeof ZSurveyElementChoice>;
export const ZShuffleOption = z.enum(["none", "all", "exceptLast"]);
export type TShuffleOption = z.infer<typeof ZShuffleOption>;