Skip to main content
Glama
form-config.ts61 kB
/** * Form Configuration Data Models * * This file contains TypeScript interfaces and Zod schemas for form configuration, * questions, validation rules, and conditional logic. These models are used for * creating and modifying Tally forms through natural language processing. */ // =============================== // Enums and Constants // =============================== /** * Supported question types for Tally forms */ export enum QuestionType { TEXT = 'text', EMAIL = 'email', NUMBER = 'number', CHOICE = 'choice', RATING = 'rating', FILE = 'file', DATE = 'date', TIME = 'time', TEXTAREA = 'textarea', DROPDOWN = 'dropdown', CHECKBOXES = 'checkboxes', LINEAR_SCALE = 'linear_scale', MULTIPLE_CHOICE = 'multiple_choice', PHONE = 'phone', URL = 'url', SIGNATURE = 'signature', PAYMENT = 'payment', MATRIX = 'matrix', } /** * Form display themes and styles */ export enum FormTheme { DEFAULT = 'default', MINIMAL = 'minimal', MODERN = 'modern', CLASSIC = 'classic', CUSTOM = 'custom', } /** * Form submission behavior options */ export enum SubmissionBehavior { REDIRECT = 'redirect', MESSAGE = 'message', CLOSE = 'close', RELOAD = 'reload', } /** * Validation rule types */ export enum ValidationType { REQUIRED = 'required', MIN_LENGTH = 'min_length', MAX_LENGTH = 'max_length', MIN_VALUE = 'min_value', MAX_VALUE = 'max_value', PATTERN = 'pattern', EMAIL_FORMAT = 'email_format', URL_FORMAT = 'url_format', PHONE_FORMAT = 'phone_format', DATE_RANGE = 'date_range', FILE_TYPE = 'file_type', FILE_SIZE = 'file_size', } /** * Conditional logic operators */ export enum LogicOperator { EQUALS = 'equals', NOT_EQUALS = 'not_equals', CONTAINS = 'contains', NOT_CONTAINS = 'not_contains', GREATER_THAN = 'greater_than', LESS_THAN = 'less_than', GREATER_EQUAL = 'greater_equal', LESS_EQUAL = 'less_equal', IS_EMPTY = 'is_empty', IS_NOT_EMPTY = 'is_not_empty', } /** * Question layout options for choice-based questions */ export enum QuestionLayout { VERTICAL = 'vertical', HORIZONTAL = 'horizontal', GRID = 'grid', } /** * Rating display styles */ export enum RatingStyle { STARS = 'stars', NUMBERS = 'numbers', THUMBS = 'thumbs', HEARTS = 'hearts', FACES = 'faces', } /** * Phone number format types */ export enum PhoneFormat { US = 'US', INTERNATIONAL = 'INTERNATIONAL', CUSTOM = 'CUSTOM', } /** * Time format options */ export enum TimeFormat { TWELVE_HOUR = '12', TWENTY_FOUR_HOUR = '24', } /** * Payment methods */ export enum PaymentMethod { CARD = 'card', PAYPAL = 'paypal', APPLE_PAY = 'apple_pay', GOOGLE_PAY = 'google_pay', } /** * Matrix question response types */ export enum MatrixResponseType { SINGLE_SELECT = 'single_select', MULTI_SELECT = 'multi_select', TEXT_INPUT = 'text_input', RATING = 'rating', } /** * Configuration for matrix cell validation */ export interface MatrixCellValidation { /** * Whether this cell is required */ required?: boolean | undefined; /** * For text input cells: minimum character count */ minLength?: number | undefined; /** * For text input cells: maximum character count */ maxLength?: number | undefined; /** * For rating cells: minimum rating value */ minRating?: number | undefined; /** * For rating cells: maximum rating value */ maxRating?: number | undefined; /** * Custom validation pattern */ pattern?: string | undefined; /** * Custom error message */ errorMessage?: string | undefined; } /** * Matrix question row definition */ export interface MatrixRow { /** * Unique identifier for the row */ id?: string | undefined; /** * Display text for the row */ text: string; /** * Optional value (defaults to text if not provided) */ value?: string | undefined; /** * Whether this row is required */ required?: boolean | undefined; /** * Cell-specific validation for this row */ cellValidation?: MatrixCellValidation | undefined; } /** * Matrix question column definition */ export interface MatrixColumn { /** * Unique identifier for the column */ id?: string | undefined; /** * Display text for the column */ text: string; /** * Optional value (defaults to text if not provided) */ value?: string | undefined; /** * Response type for this column */ responseType?: MatrixResponseType | undefined; /** * For choice columns: available options */ options?: QuestionOption[] | undefined; /** * Column width (for responsive layouts) */ width?: string | undefined; } // =============================== // Core Interfaces // =============================== /** * Base form configuration interface * * Defines the core structure for creating and configuring Tally forms. * This interface is extensible to support future form properties and features. */ export interface FormConfig { /** * The title of the form, displayed at the top */ title: string; /** * Optional description or subtitle for the form */ description?: string | undefined; /** * Array of questions/fields in the form */ questions: QuestionConfig[]; /** * Form-level settings and configuration */ settings: FormSettings; /** * Optional branding and styling configuration */ branding?: BrandingConfig | undefined; /** * Optional metadata for form organization */ metadata?: FormMetadata | undefined; } /** * Base question configuration interface * * Contains common properties shared by all question types. */ export interface BaseQuestionConfig { /** * Unique identifier for the question (auto-generated if not provided) */ id?: string | undefined; /** * Display label for the question */ label: string; /** * Optional description or help text for the question */ description?: string | undefined; /** * Whether the question is required for form submission */ required: boolean; /** * Placeholder text for input fields */ placeholder?: string | undefined; /** * Validation rules for the question */ validation?: ValidationRules | undefined; /** * Conditional logic rules for showing/hiding this question */ logic?: ConditionalLogic | undefined; /** * Display order/position in the form */ order?: number | undefined; } // =============================== // Specific Question Type Interfaces // =============================== /** * Text input question configuration */ export interface TextQuestionConfig extends BaseQuestionConfig { type: QuestionType.TEXT; /** * Minimum character count */ minLength?: number | undefined; /** * Maximum character count */ maxLength?: number | undefined; /** * Input format hint (e.g., 'capitalize', 'lowercase', 'uppercase') */ format?: string | undefined; } /** * Textarea (long text) question configuration */ export interface TextareaQuestionConfig extends BaseQuestionConfig { type: QuestionType.TEXTAREA; /** * Minimum character count */ minLength?: number | undefined; /** * Maximum character count */ maxLength?: number | undefined; /** * Number of visible rows */ rows?: number | undefined; /** * Whether to auto-resize based on content */ autoResize?: boolean | undefined; } /** * Email input question configuration */ export interface EmailQuestionConfig extends BaseQuestionConfig { type: QuestionType.EMAIL; /** * Whether to validate email format */ validateFormat?: boolean | undefined; /** * Whether to suggest common email domains */ suggestDomains?: boolean | undefined; } /** * Phone number input question configuration */ export interface PhoneQuestionConfig extends BaseQuestionConfig { type: QuestionType.PHONE; /** * Phone number format/pattern */ format?: PhoneFormat | undefined; /** * Custom format pattern if using CUSTOM format */ customPattern?: string | undefined; /** * Whether to auto-format as user types */ autoFormat?: boolean | undefined; } /** * URL input question configuration */ export interface UrlQuestionConfig extends BaseQuestionConfig { type: QuestionType.URL; /** * Whether to validate URL format */ validateFormat?: boolean | undefined; /** * Whether to require specific URL schemes (e.g., ['http', 'https']) */ allowedSchemes?: string[] | undefined; } /** * Number input question configuration */ export interface NumberQuestionConfig extends BaseQuestionConfig { type: QuestionType.NUMBER; /** * Minimum allowed value */ min?: number | undefined; /** * Maximum allowed value */ max?: number | undefined; /** * Step value for increment/decrement */ step?: number | undefined; /** * Number of decimal places allowed */ decimalPlaces?: number | undefined; /** * Whether to format with thousand separators */ useThousandSeparator?: boolean | undefined; /** * Currency symbol (if this is a currency input) */ currency?: string | undefined; } /** * Date input question configuration */ export interface DateQuestionConfig extends BaseQuestionConfig { type: QuestionType.DATE; /** * Minimum allowed date (ISO string) */ minDate?: string | undefined; /** * Maximum allowed date (ISO string) */ maxDate?: string | undefined; /** * Date format for display (e.g., 'MM/DD/YYYY', 'DD/MM/YYYY') */ dateFormat?: string | undefined; /** * Whether to include time selection */ includeTime?: boolean | undefined; /** * Default date value (ISO string) */ defaultDate?: string | undefined; } /** * Time input question configuration */ export interface TimeQuestionConfig extends BaseQuestionConfig { type: QuestionType.TIME; /** * Time format (12-hour or 24-hour) */ format?: TimeFormat | undefined; /** * Minute step interval */ minuteStep?: number | undefined; /** * Default time value (HH:MM format) */ defaultTime?: string | undefined; } /** * Multiple choice question configuration */ export interface MultipleChoiceQuestionConfig extends BaseQuestionConfig { type: QuestionType.MULTIPLE_CHOICE; /** * Available options for selection */ options: QuestionOption[]; /** * Whether to allow "Other" option with text input */ allowOther?: boolean | undefined; /** * Whether to randomize option order */ randomizeOptions?: boolean | undefined; /** * Layout style for options */ layout?: QuestionLayout | undefined; } /** * Dropdown question configuration */ export interface DropdownQuestionConfig extends BaseQuestionConfig { type: QuestionType.DROPDOWN; /** * Available options for selection */ options: QuestionOption[]; /** * Whether to allow "Other" option with text input */ allowOther?: boolean | undefined; /** * Whether to allow search/filtering of options */ searchable?: boolean | undefined; /** * Placeholder text for the dropdown */ dropdownPlaceholder?: string | undefined; /** * Whether to allow multiple selections */ multiSelect?: boolean | undefined; /** * Maximum number of selections (for multi-select) */ maxSelections?: number | undefined; /** * Minimum number of selections (for multi-select) */ minSelections?: number | undefined; /** * Whether to enable image-based options */ imageOptions?: boolean | undefined; /** * Advanced search configuration */ searchConfig?: { minSearchLength?: number | undefined; searchPlaceholder?: string | undefined; highlightMatches?: boolean | undefined; fuzzySearch?: boolean | undefined; } | undefined; } /** * Checkboxes (multi-select) question configuration */ export interface CheckboxesQuestionConfig extends BaseQuestionConfig { type: QuestionType.CHECKBOXES; /** * Available options for selection */ options: QuestionOption[]; /** * Minimum number of required selections */ minSelections?: number | undefined; /** * Maximum number of allowed selections */ maxSelections?: number | undefined; /** * Whether to allow "Other" option with text input */ allowOther?: boolean | undefined; /** * Whether to randomize option order */ randomizeOptions?: boolean | undefined; /** * Layout style for options */ layout?: QuestionLayout | undefined; /** * Whether to enable image-based options */ imageOptions?: boolean | undefined; /** * Advanced selection constraints */ selectionConstraints?: { forbiddenCombinations?: string[][] | undefined; requiredCombinations?: string[][] | undefined; mutuallyExclusive?: string[][] | undefined; } | undefined; /** * Search and filtering options for large option sets */ searchOptions?: { enableSearch?: boolean | undefined; searchPlaceholder?: string | undefined; minSearchLength?: number | undefined; } | undefined; } /** * Rating question configuration */ export interface RatingQuestionConfig extends BaseQuestionConfig { type: QuestionType.RATING; /** * Minimum rating value */ minRating: number; /** * Maximum rating value */ maxRating: number; /** * Labels for rating values (optional) */ ratingLabels?: string[] | undefined; /** * Rating display style */ style?: RatingStyle | undefined; /** * Whether to show numeric values alongside icons */ showNumbers?: boolean | undefined; /** * Label for low end of scale */ lowLabel?: string | undefined; /** * Label for high end of scale */ highLabel?: string | undefined; } /** * Linear scale question configuration */ export interface LinearScaleQuestionConfig extends BaseQuestionConfig { type: QuestionType.LINEAR_SCALE; /** * Minimum scale value */ minValue: number; /** * Maximum scale value */ maxValue: number; /** * Step interval for the scale */ step?: number | undefined; /** * Label for low end of scale */ lowLabel?: string | undefined; /** * Label for high end of scale */ highLabel?: string | undefined; /** * Whether to show numeric values on scale */ showNumbers?: boolean | undefined; } /** * File upload question configuration */ export interface FileQuestionConfig extends BaseQuestionConfig { type: QuestionType.FILE; /** * Allowed file types (MIME types or extensions) */ allowedTypes?: string[] | undefined; /** * Maximum file size in MB */ maxFileSize?: number | undefined; /** * Maximum number of files allowed */ maxFiles?: number | undefined; /** * Whether to allow multiple file selection */ multiple?: boolean | undefined; /** * Upload area text/instructions */ uploadText?: string | undefined; /** * Whether to enable drag-and-drop functionality */ enableDragDrop?: boolean | undefined; /** * Hint text for drag-and-drop area */ dragDropHint?: string | undefined; /** * Whether to show upload progress indicator */ showProgress?: boolean | undefined; /** * Whether to show file preview thumbnails */ showPreview?: boolean | undefined; /** * File type restrictions for better validation */ fileRestrictions?: { allowedExtensions?: string[] | undefined; blockedExtensions?: string[] | undefined; allowedMimeTypes?: string[] | undefined; blockedMimeTypes?: string[] | undefined; } | undefined; /** * Upload size constraints */ sizeConstraints?: { minFileSize?: number | undefined; // in MB maxTotalSize?: number | undefined; // in MB (for multiple files) } | undefined; } /** * Signature question configuration */ export interface SignatureQuestionConfig extends BaseQuestionConfig { type: QuestionType.SIGNATURE; /** * Canvas width in pixels */ canvasWidth?: number | undefined; /** * Canvas height in pixels */ canvasHeight?: number | undefined; /** * Pen color for signature */ penColor?: string | undefined; /** * Background color for signature pad */ backgroundColor?: string | undefined; } /** * Matrix question configuration */ export interface MatrixQuestionConfig extends BaseQuestionConfig { type: QuestionType.MATRIX; /** * Matrix rows (question statements) */ rows: MatrixRow[]; /** * Matrix columns (answer options/types) */ columns: MatrixColumn[]; /** * Default response type for all cells */ defaultResponseType: MatrixResponseType; /** * Whether to allow selecting multiple options per row (for multi-select type) */ allowMultiplePerRow?: boolean | undefined; /** * Whether to require at least one selection per row */ requireAllRows?: boolean | undefined; /** * Whether to randomize row order */ randomizeRows?: boolean | undefined; /** * Whether to randomize column order */ randomizeColumns?: boolean | undefined; /** * Mobile responsive behavior */ mobileLayout?: 'stacked' | 'scrollable' | 'accordion' | undefined; /** * Whether to show row/column headers on mobile */ showHeadersOnMobile?: boolean | undefined; /** * Default validation for all cells */ defaultCellValidation?: MatrixCellValidation | undefined; /** * Custom CSS classes for styling */ customClasses?: { table?: string | undefined; row?: string | undefined; column?: string | undefined; cell?: string | undefined; } | undefined; } /** * Payment question configuration */ export interface PaymentQuestionConfig extends BaseQuestionConfig { type: QuestionType.PAYMENT; /** * Payment amount in cents */ amount?: number | undefined; /** * Currency code (e.g., 'USD', 'EUR') */ currency: string; /** * Whether amount is fixed or user can enter custom amount */ fixedAmount?: boolean | undefined; /** * Minimum amount if allowing custom amounts */ minAmount?: number | undefined; /** * Maximum amount if allowing custom amounts */ maxAmount?: number | undefined; /** * Payment description */ paymentDescription?: string | undefined; /** * Accepted payment methods */ acceptedMethods?: PaymentMethod[] | undefined; } /** * Discriminated union of all question configuration types * * This union allows TypeScript to provide proper type discrimination * based on the 'type' property, ensuring type safety when working * with different question configurations. */ export type QuestionConfig = | TextQuestionConfig | TextareaQuestionConfig | EmailQuestionConfig | PhoneQuestionConfig | UrlQuestionConfig | NumberQuestionConfig | DateQuestionConfig | TimeQuestionConfig | MultipleChoiceQuestionConfig | DropdownQuestionConfig | CheckboxesQuestionConfig | RatingQuestionConfig | LinearScaleQuestionConfig | FileQuestionConfig | SignatureQuestionConfig | MatrixQuestionConfig | PaymentQuestionConfig; // =============================== // Type Guards and Utility Types // =============================== /** * Type guard to check if a question has options */ export function questionHasOptions(question: QuestionConfig): question is | MultipleChoiceQuestionConfig | DropdownQuestionConfig | CheckboxesQuestionConfig { return question.type === QuestionType.MULTIPLE_CHOICE || question.type === QuestionType.DROPDOWN || question.type === QuestionType.CHECKBOXES; } /** * Type guard to check if a question is text-based */ export function isTextBasedQuestion(question: QuestionConfig): question is | TextQuestionConfig | TextareaQuestionConfig | EmailQuestionConfig | PhoneQuestionConfig | UrlQuestionConfig { return question.type === QuestionType.TEXT || question.type === QuestionType.TEXTAREA || question.type === QuestionType.EMAIL || question.type === QuestionType.PHONE || question.type === QuestionType.URL; } /** * Type guard to check if a question is numeric */ export function isNumericQuestion(question: QuestionConfig): question is | NumberQuestionConfig | RatingQuestionConfig | LinearScaleQuestionConfig { return question.type === QuestionType.NUMBER || question.type === QuestionType.RATING || question.type === QuestionType.LINEAR_SCALE; } /** * Type guard to check if a question is date/time related */ export function isDateTimeQuestion(question: QuestionConfig): question is | DateQuestionConfig | TimeQuestionConfig { return question.type === QuestionType.DATE || question.type === QuestionType.TIME; } /** * Type guard to check if a question supports file uploads */ export function isFileQuestion(question: QuestionConfig): question is | FileQuestionConfig | SignatureQuestionConfig { return question.type === QuestionType.FILE || question.type === QuestionType.SIGNATURE; } /** * Type guard to check if a question is a matrix question */ export function isMatrixQuestion(question: QuestionConfig): question is MatrixQuestionConfig { return question.type === QuestionType.MATRIX; } /** * Helper type to extract question type from question config */ export type QuestionTypeOf<T extends QuestionConfig> = T['type']; /** * Helper type to get question config by type */ export type QuestionConfigByType<T extends QuestionType> = T extends QuestionType.TEXT ? TextQuestionConfig : T extends QuestionType.TEXTAREA ? TextareaQuestionConfig : T extends QuestionType.EMAIL ? EmailQuestionConfig : T extends QuestionType.PHONE ? PhoneQuestionConfig : T extends QuestionType.URL ? UrlQuestionConfig : T extends QuestionType.NUMBER ? NumberQuestionConfig : T extends QuestionType.DATE ? DateQuestionConfig : T extends QuestionType.TIME ? TimeQuestionConfig : T extends QuestionType.MULTIPLE_CHOICE ? MultipleChoiceQuestionConfig : T extends QuestionType.DROPDOWN ? DropdownQuestionConfig : T extends QuestionType.CHECKBOXES ? CheckboxesQuestionConfig : T extends QuestionType.RATING ? RatingQuestionConfig : T extends QuestionType.LINEAR_SCALE ? LinearScaleQuestionConfig : T extends QuestionType.FILE ? FileQuestionConfig : T extends QuestionType.SIGNATURE ? SignatureQuestionConfig : T extends QuestionType.MATRIX ? MatrixQuestionConfig : T extends QuestionType.PAYMENT ? PaymentQuestionConfig : never; /** * Utility type for question configuration factories */ export type QuestionConfigFactory<T extends QuestionType> = ( baseConfig: Omit<BaseQuestionConfig, 'id'> & { id?: string } ) => QuestionConfigByType<T>; /** * All question types that support choice options */ export type ChoiceQuestionType = | QuestionType.MULTIPLE_CHOICE | QuestionType.DROPDOWN | QuestionType.CHECKBOXES; /** * All question types that support numeric input/validation */ export type NumericQuestionType = | QuestionType.NUMBER | QuestionType.RATING | QuestionType.LINEAR_SCALE; /** * All question types that support text input/validation */ export type TextQuestionType = | QuestionType.TEXT | QuestionType.TEXTAREA | QuestionType.EMAIL | QuestionType.PHONE | QuestionType.URL; // =============================== // Validation Rule Type Guards and Utilities // =============================== /** * Type guard for required validation */ export function isRequiredValidation(rule: ValidationRule): rule is RequiredValidation { return rule.type === 'required'; } /** * Type guard for length validation */ export function isLengthValidation(rule: ValidationRule): rule is LengthValidation { return rule.type === 'length'; } /** * Type guard for numeric validation */ export function isNumericValidation(rule: ValidationRule): rule is NumericValidation { return rule.type === 'numeric'; } /** * Type guard for pattern validation */ export function isPatternValidation(rule: ValidationRule): rule is PatternValidation { return rule.type === 'pattern'; } /** * Type guard for email validation */ export function isEmailValidation(rule: ValidationRule): rule is EmailValidation { return rule.type === 'email'; } /** * Type guard for URL validation */ export function isUrlValidation(rule: ValidationRule): rule is UrlValidation { return rule.type === 'url'; } /** * Type guard for phone validation */ export function isPhoneValidation(rule: ValidationRule): rule is PhoneValidation { return rule.type === 'phone'; } /** * Type guard for date validation */ export function isDateValidation(rule: ValidationRule): rule is DateValidation { return rule.type === 'date'; } /** * Type guard for time validation */ export function isTimeValidation(rule: ValidationRule): rule is TimeValidation { return rule.type === 'time'; } /** * Type guard for file validation */ export function isFileValidation(rule: ValidationRule): rule is FileValidation { return rule.type === 'file'; } /** * Type guard for choice validation */ export function isChoiceValidation(rule: ValidationRule): rule is ChoiceValidation { return rule.type === 'choice'; } /** * Type guard for rating validation */ export function isRatingValidation(rule: ValidationRule): rule is RatingValidation { return rule.type === 'rating'; } /** * Type guard for custom validation */ export function isCustomValidation(rule: ValidationRule): rule is CustomValidation { return rule.type === 'custom'; } /** * Get compatible validation rules for a specific question type */ export function getCompatibleValidationTypes(questionType: QuestionType): ValidationRule['type'][] { const baseTypes: ValidationRule['type'][] = ['required', 'custom']; switch (questionType) { case QuestionType.TEXT: case QuestionType.TEXTAREA: return [...baseTypes, 'length', 'pattern']; case QuestionType.EMAIL: return [...baseTypes, 'length', 'pattern', 'email']; case QuestionType.PHONE: return [...baseTypes, 'length', 'pattern', 'phone']; case QuestionType.URL: return [...baseTypes, 'length', 'pattern', 'url']; case QuestionType.NUMBER: return [...baseTypes, 'numeric']; case QuestionType.DATE: return [...baseTypes, 'date']; case QuestionType.TIME: return [...baseTypes, 'time']; case QuestionType.MULTIPLE_CHOICE: case QuestionType.DROPDOWN: case QuestionType.CHECKBOXES: return [...baseTypes, 'choice']; case QuestionType.RATING: case QuestionType.LINEAR_SCALE: return [...baseTypes, 'rating', 'numeric']; case QuestionType.FILE: case QuestionType.SIGNATURE: return [...baseTypes, 'file']; case QuestionType.MATRIX: return [...baseTypes, 'choice']; case QuestionType.PAYMENT: return [...baseTypes, 'numeric']; default: return baseTypes; } } /** * Check if a validation rule is compatible with a question type */ export function isValidationRuleCompatible( rule: ValidationRule, questionType: QuestionType ): boolean { const compatibleTypes = getCompatibleValidationTypes(questionType); return compatibleTypes.includes(rule.type); } /** * Filter validation rules to only include compatible ones for a question type */ export function filterCompatibleValidationRules( rules: ValidationRule[], questionType: QuestionType ): ValidationRule[] { return rules.filter(rule => isValidationRuleCompatible(rule, questionType)); } /** * Utility type for validation rule configurations by question type */ export type ValidationRulesForQuestionType<T extends QuestionType> = T extends TextQuestionType ? (RequiredValidation | LengthValidation | PatternValidation | CustomValidation) : T extends QuestionType.EMAIL ? (RequiredValidation | LengthValidation | PatternValidation | EmailValidation | CustomValidation) : T extends QuestionType.PHONE ? (RequiredValidation | LengthValidation | PatternValidation | PhoneValidation | CustomValidation) : T extends QuestionType.URL ? (RequiredValidation | LengthValidation | PatternValidation | UrlValidation | CustomValidation) : T extends NumericQuestionType ? (RequiredValidation | NumericValidation | CustomValidation) : T extends QuestionType.DATE ? (RequiredValidation | DateValidation | CustomValidation) : T extends QuestionType.TIME ? (RequiredValidation | TimeValidation | CustomValidation) : T extends ChoiceQuestionType ? (RequiredValidation | ChoiceValidation | CustomValidation) : T extends QuestionType.FILE | QuestionType.SIGNATURE ? (RequiredValidation | FileValidation | CustomValidation) : T extends QuestionType.MATRIX ? (RequiredValidation | ChoiceValidation | CustomValidation) : T extends QuestionType.PAYMENT ? (RequiredValidation | NumericValidation | CustomValidation) : RequiredValidation | CustomValidation; /** * Utility function to create typed validation rules for a specific question type */ export function createValidationRulesForQuestion<T extends QuestionType>( rules: ValidationRulesForQuestionType<T>[] ): ValidationRules { return { rules: rules as ValidationRule[], validateOnChange: true, validateOnBlur: true, stopOnFirstError: false, }; } /** * Combine multiple validation rule sets */ export function combineValidationRules(...ruleSets: ValidationRules[]): ValidationRules { const combinedRules: ValidationRule[] = []; const combinedCustomMessages: Record<string, string> = {}; let validateOnChange = false; let validateOnBlur = false; let stopOnFirstError = false; const allDependencies: string[] = []; for (const ruleSet of ruleSets) { if (ruleSet.rules) { combinedRules.push(...ruleSet.rules); } if (ruleSet.customMessages) { Object.assign(combinedCustomMessages, ruleSet.customMessages); } if (ruleSet.validateOnChange) validateOnChange = true; if (ruleSet.validateOnBlur) validateOnBlur = true; if (ruleSet.stopOnFirstError) stopOnFirstError = true; if (ruleSet.dependencies) { allDependencies.push(...ruleSet.dependencies); } } const result: ValidationRules = { rules: combinedRules, validateOnChange, validateOnBlur, stopOnFirstError, }; if (Object.keys(combinedCustomMessages).length > 0) { result.customMessages = combinedCustomMessages as Partial<Record<ValidationRule['type'], string>>; } if (allDependencies.length > 0) { result.dependencies = [...new Set(allDependencies)]; } return result; } /** * Helper function to create a required validation rule */ export function createRequiredRule(errorMessage?: string): RequiredValidation { const rule: RequiredValidation = { type: 'required', required: true, }; if (errorMessage !== undefined) { rule.errorMessage = errorMessage; } return rule; } /** * Helper function to create a length validation rule */ export function createLengthRule( options: { minLength?: number; maxLength?: number; errorMessage?: string } ): LengthValidation { const rule: LengthValidation = { type: 'length', }; if (options.minLength !== undefined) rule.minLength = options.minLength; if (options.maxLength !== undefined) rule.maxLength = options.maxLength; if (options.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } /** * Helper function to create a numeric validation rule */ export function createNumericRule( options: { min?: number; max?: number; step?: number; decimalPlaces?: number; errorMessage?: string } ): NumericValidation { const rule: NumericValidation = { type: 'numeric', }; if (options.min !== undefined) rule.min = options.min; if (options.max !== undefined) rule.max = options.max; if (options.step !== undefined) rule.step = options.step; if (options.decimalPlaces !== undefined) rule.decimalPlaces = options.decimalPlaces; if (options.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } /** * Helper function to create a pattern validation rule */ export function createPatternRule( pattern: string, options?: { flags?: string; caseSensitive?: boolean; errorMessage?: string } ): PatternValidation { const rule: PatternValidation = { type: 'pattern', pattern, }; if (options?.flags !== undefined) rule.flags = options.flags; if (options?.caseSensitive !== undefined) rule.caseSensitive = options.caseSensitive; if (options?.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } /** * Helper function to create an email validation rule */ export function createEmailRule( options?: { allowedDomains?: string[]; blockedDomains?: string[]; requireTLD?: boolean; errorMessage?: string } ): EmailValidation { const rule: EmailValidation = { type: 'email', }; if (options?.allowedDomains !== undefined) rule.allowedDomains = options.allowedDomains; if (options?.blockedDomains !== undefined) rule.blockedDomains = options.blockedDomains; if (options?.requireTLD !== undefined) rule.requireTLD = options.requireTLD; if (options?.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } /** * Helper function to create a choice validation rule */ export function createChoiceRule( options: { minSelections?: number; maxSelections?: number; requiredOptions?: string[]; forbiddenCombinations?: string[][]; errorMessage?: string } ): ChoiceValidation { const rule: ChoiceValidation = { type: 'choice', }; if (options.minSelections !== undefined) rule.minSelections = options.minSelections; if (options.maxSelections !== undefined) rule.maxSelections = options.maxSelections; if (options.requiredOptions !== undefined) rule.requiredOptions = options.requiredOptions; if (options.forbiddenCombinations !== undefined) rule.forbiddenCombinations = options.forbiddenCombinations; if (options.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } // ======================================= // Conditional Logic Type Guards & Utils // ======================================= /** * Type guard to check if an action is a show/hide action */ export function isShowHideAction(action: ConditionalActionConfig): action is ShowHideAction { return action.action === ConditionalAction.SHOW || action.action === ConditionalAction.HIDE; } /** * Type guard to check if an action is a require action */ export function isRequireAction(action: ConditionalActionConfig): action is RequireAction { return action.action === ConditionalAction.REQUIRE || action.action === ConditionalAction.MAKE_OPTIONAL; } /** * Type guard to check if an action is a jump to action */ export function isJumpToAction(action: ConditionalActionConfig): action is JumpToAction { return action.action === ConditionalAction.JUMP_TO; } /** * Type guard to check if an action is a jump to page action */ export function isJumpToPageAction(action: ConditionalActionConfig): action is JumpToPageAction { return action.action === ConditionalAction.JUMP_TO_PAGE; } /** * Type guard to check if an action is a set value action */ export function isSetValueAction(action: ConditionalActionConfig): action is SetValueAction { return action.action === ConditionalAction.SET_VALUE; } /** * Type guard to check if an action is a clear value action */ export function isClearValueAction(action: ConditionalActionConfig): action is ClearValueAction { return action.action === ConditionalAction.CLEAR_VALUE; } /** * Type guard to check if an action is an enable/disable action */ export function isEnableDisableAction(action: ConditionalActionConfig): action is EnableDisableAction { return action.action === ConditionalAction.ENABLE || action.action === ConditionalAction.DISABLE; } /** * Type guard to check if an action is a show message action */ export function isShowMessageAction(action: ConditionalActionConfig): action is ShowMessageAction { return action.action === ConditionalAction.SHOW_MESSAGE; } /** * Type guard to check if an action is a redirect action */ export function isRedirectAction(action: ConditionalActionConfig): action is RedirectAction { return action.action === ConditionalAction.REDIRECT; } /** * Type guard to check if an action is a submit form action */ export function isSubmitFormAction(action: ConditionalActionConfig): action is SubmitFormAction { return action.action === ConditionalAction.SUBMIT_FORM; } /** * Type guard to check if an action is a skip action */ export function isSkipAction(action: ConditionalActionConfig): action is SkipAction { return action.action === ConditionalAction.SKIP; } /** * Get all question IDs referenced in a conditional logic rule */ export function getReferencedQuestionIds(logic: ConditionalLogic): string[] { const questionIds = new Set<string>(); function processConditionGroup(group: LogicConditionGroup): void { group.conditions.forEach(condition => { questionIds.add(condition.questionId); }); group.groups?.forEach(nestedGroup => { processConditionGroup(nestedGroup); }); } function processActions(actions: ConditionalActionConfig[]): void { actions.forEach(action => { if (isJumpToAction(action)) { questionIds.add(action.targetQuestionId); } else if (isSetValueAction(action) || isClearValueAction(action)) { questionIds.add(action.targetQuestionId); } }); } processConditionGroup(logic.conditionGroup); processActions(logic.actions); if (logic.elseActions) { processActions(logic.elseActions); } return Array.from(questionIds); } /** * Check if a conditional logic rule references a specific question */ export function logicReferencesQuestion(logic: ConditionalLogic, questionId: string): boolean { return getReferencedQuestionIds(logic).includes(questionId); } /** * Validate that all referenced questions exist in the form */ export function validateLogicReferences( logic: ConditionalLogic, existingQuestionIds: string[] ): { isValid: boolean; missingQuestions: string[] } { const referencedIds = getReferencedQuestionIds(logic); const missingQuestions = referencedIds.filter(id => !existingQuestionIds.includes(id)); return { isValid: missingQuestions.length === 0, missingQuestions }; } /** * Create a simple show/hide conditional logic rule */ export function createSimpleShowHideLogic( questionId: string, triggerQuestionId: string, operator: LogicOperator, value: any, action: 'show' | 'hide' = 'show' ): ConditionalLogic { return { id: `${action}_${questionId}_when_${triggerQuestionId}_${operator}_${value}`, name: `${action === 'show' ? 'Show' : 'Hide'} ${questionId} when ${triggerQuestionId} ${operator} ${value}`, enabled: true, conditionGroup: { combinator: LogicCombinator.AND, conditions: [ { questionId: triggerQuestionId, operator, value } ] }, actions: [ { action: action === 'show' ? ConditionalAction.SHOW : ConditionalAction.HIDE } as ShowHideAction ] }; } /** * Create a conditional logic rule for making questions required */ export function createRequiredLogic( questionId: string, triggerQuestionId: string, operator: LogicOperator, value: any, validationMessage?: string ): ConditionalLogic { return { id: `require_${questionId}_when_${triggerQuestionId}_${operator}_${value}`, name: `Require ${questionId} when ${triggerQuestionId} ${operator} ${value}`, enabled: true, conditionGroup: { combinator: LogicCombinator.AND, conditions: [ { questionId: triggerQuestionId, operator, value } ] }, actions: [ { action: ConditionalAction.REQUIRE, validationMessage } as RequireAction ] }; } /** * Create a jump logic rule */ export function createJumpLogic( triggerQuestionId: string, operator: LogicOperator, value: any, targetQuestionId: string, skipValidation: boolean = false ): ConditionalLogic { return { id: `jump_to_${targetQuestionId}_when_${triggerQuestionId}_${operator}_${value}`, name: `Jump to ${targetQuestionId} when ${triggerQuestionId} ${operator} ${value}`, enabled: true, conditionGroup: { combinator: LogicCombinator.AND, conditions: [ { questionId: triggerQuestionId, operator, value } ] }, actions: [ { action: ConditionalAction.JUMP_TO, targetQuestionId, skipValidation } as JumpToAction ] }; } /** * Utility type for actions that target specific questions */ export type QuestionTargetingAction = JumpToAction | SetValueAction | ClearValueAction; /** * Utility type for actions that affect question behavior */ export type QuestionBehaviorAction = ShowHideAction | RequireAction | EnableDisableAction; /** * Utility type for actions that affect form flow */ export type FormFlowAction = JumpToAction | JumpToPageAction | SubmitFormAction | SkipAction; /** * Utility type for actions that provide user feedback */ export type FeedbackAction = ShowMessageAction | RedirectAction; /** * Form-level settings and behavior configuration */ export interface FormSettings { /** * Whether to show a progress bar */ showProgressBar?: boolean | undefined; /** * Whether to allow saving drafts */ allowDrafts?: boolean | undefined; /** * Whether to show question numbers */ showQuestionNumbers?: boolean | undefined; /** * Whether to shuffle question order */ shuffleQuestions?: boolean | undefined; /** * Maximum number of submissions allowed */ maxSubmissions?: number | undefined; /** * Whether to require authentication */ requireAuth?: boolean | undefined; /** * Whether to collect respondent email */ collectEmail?: boolean | undefined; /** * Form closing date (ISO string) */ closeDate?: string | undefined; /** * Form opening date (ISO string) */ openDate?: string | undefined; /** * Behavior after form submission */ submissionBehavior: SubmissionBehavior; /** * Custom message or URL for after submission */ submissionMessage?: string | undefined; /** * Redirect URL after submission (if using REDIRECT behavior) */ redirectUrl?: string | undefined; /** * Whether to send confirmation emails */ sendConfirmationEmail?: boolean | undefined; /** * Whether to allow multiple submissions from same user */ allowMultipleSubmissions?: boolean | undefined; } /** * Form branding and styling configuration */ export interface BrandingConfig { /** * Form theme/style */ theme: FormTheme; /** * Primary brand color (hex code) */ primaryColor?: string | undefined; /** * Secondary brand color (hex code) */ secondaryColor?: string | undefined; /** * Background color or image URL */ background?: string | undefined; /** * Custom CSS styles */ customCss?: string | undefined; /** * Logo image URL */ logoUrl?: string | undefined; /** * Font family name */ fontFamily?: string | undefined; /** * Button style customization */ buttonStyle?: ButtonStyle | undefined; } /** * Form metadata for organization and management */ export interface FormMetadata { /** * Tags for categorizing the form */ tags?: string[] | undefined; /** * Category or folder for organization */ category?: string | undefined; /** * Form creation timestamp */ createdAt?: string | undefined; /** * Last modification timestamp */ updatedAt?: string | undefined; /** * Form creator/owner identifier */ createdBy?: string | undefined; /** * Workspace or organization identifier */ workspaceId?: string | undefined; /** * Whether the form is published/public */ isPublished?: boolean | undefined; /** * Whether the form is archived */ isArchived?: boolean | undefined; /** * Form version number */ version?: number | undefined; } /** * Button styling configuration */ export interface ButtonStyle { /** * Button background color */ backgroundColor?: string | undefined; /** * Button text color */ textColor?: string | undefined; /** * Button border radius */ borderRadius?: number | undefined; /** * Button border style */ border?: string | undefined; /** * Button font size */ fontSize?: number | undefined; /** * Button padding */ padding?: string | undefined; } /** * Option for choice-based questions */ export interface QuestionOption { /** * Unique identifier for the option */ id?: string | undefined; /** * Display text for the option */ text: string; /** * Optional value (defaults to text if not provided) */ value?: string | undefined; /** * Whether this option is selected by default */ isDefault?: boolean | undefined; /** * Optional image URL for the option */ imageUrl?: string | undefined; /** * Additional metadata for the option */ metadata?: Record<string, any> | undefined; } // =============================== // Validation Rules and Types // =============================== /** * Base validation rule interface */ export interface BaseValidationRule { /** * Custom error message for this validation rule */ errorMessage?: string | undefined; /** * Whether this validation rule is enabled */ enabled?: boolean | undefined; } /** * Required field validation */ export interface RequiredValidation extends BaseValidationRule { type: 'required'; required: true; } /** * Text length validation */ export interface LengthValidation extends BaseValidationRule { type: 'length'; minLength?: number | undefined; maxLength?: number | undefined; } /** * Numeric range validation */ export interface NumericValidation extends BaseValidationRule { type: 'numeric'; min?: number | undefined; max?: number | undefined; step?: number | undefined; decimalPlaces?: number | undefined; } /** * Pattern matching validation */ export interface PatternValidation extends BaseValidationRule { type: 'pattern'; pattern: string; flags?: string | undefined; caseSensitive?: boolean | undefined; } /** * Email format validation */ export interface EmailValidation extends BaseValidationRule { type: 'email'; allowedDomains?: string[] | undefined; blockedDomains?: string[] | undefined; requireTLD?: boolean | undefined; } /** * URL format validation */ export interface UrlValidation extends BaseValidationRule { type: 'url'; allowedSchemes?: string[] | undefined; requireScheme?: boolean | undefined; allowedDomains?: string[] | undefined; } /** * Phone number format validation */ export interface PhoneValidation extends BaseValidationRule { type: 'phone'; format?: PhoneFormat | undefined; country?: string | undefined; allowInternational?: boolean | undefined; } /** * Date range validation */ export interface DateValidation extends BaseValidationRule { type: 'date'; minDate?: string | undefined; maxDate?: string | undefined; allowPast?: boolean | undefined; allowFuture?: boolean | undefined; excludeDates?: string[] | undefined; excludeWeekends?: boolean | undefined; } /** * Time validation */ export interface TimeValidation extends BaseValidationRule { type: 'time'; minTime?: string | undefined; maxTime?: string | undefined; allowedTimeSlots?: Array<{ start: string; end: string }> | undefined; excludeTimeSlots?: Array<{ start: string; end: string }> | undefined; } /** * File upload validation */ export interface FileValidation extends BaseValidationRule { type: 'file'; allowedTypes?: string[] | undefined; blockedTypes?: string[] | undefined; maxFileSize?: number | undefined; // in MB minFileSize?: number | undefined; // in MB maxFiles?: number | undefined; minFiles?: number | undefined; allowedExtensions?: string[] | undefined; blockedExtensions?: string[] | undefined; } /** * Choice/option validation */ export interface ChoiceValidation extends BaseValidationRule { type: 'choice'; minSelections?: number | undefined; maxSelections?: number | undefined; requiredOptions?: string[] | undefined; forbiddenCombinations?: string[][] | undefined; } /** * Rating validation */ export interface RatingValidation extends BaseValidationRule { type: 'rating'; minRating?: number | undefined; maxRating?: number | undefined; requiredRating?: boolean | undefined; } /** * Custom validation with function */ export interface CustomValidation extends BaseValidationRule { type: 'custom'; validator: string | ((value: any, context?: any) => boolean | string); async?: boolean | undefined; dependencies?: string[] | undefined; // question IDs this validation depends on } /** * Union type of all validation rules */ export type ValidationRule = | RequiredValidation | LengthValidation | NumericValidation | PatternValidation | EmailValidation | UrlValidation | PhoneValidation | DateValidation | TimeValidation | FileValidation | ChoiceValidation | RatingValidation | CustomValidation; /** * Enhanced validation rules interface for form questions * * Defines validation constraints that can be applied to form questions * to ensure data quality and compliance with business rules. */ export interface ValidationRules { /** * Array of validation rules to apply */ rules?: ValidationRule[] | undefined; /** * Whether to validate on every change (real-time) or only on submit */ validateOnChange?: boolean | undefined; /** * Whether to validate on blur (when field loses focus) */ validateOnBlur?: boolean | undefined; /** * Whether to stop validation on first error */ stopOnFirstError?: boolean | undefined; /** * Custom validation messages by rule type */ customMessages?: Partial<Record<ValidationRule['type'], string>> | undefined; /** * Dependencies for cross-field validation */ dependencies?: string[] | undefined; /** * Legacy validation properties for backward compatibility */ required?: boolean | undefined; minLength?: number | undefined; maxLength?: number | undefined; minValue?: number | undefined; maxValue?: number | undefined; pattern?: string | undefined; errorMessage?: string | undefined; emailFormat?: boolean | undefined; urlFormat?: boolean | undefined; phoneFormat?: boolean | undefined; dateRange?: { min?: string | undefined; max?: string | undefined; } | undefined; fileType?: { allowed?: string[] | undefined; blocked?: string[] | undefined; } | undefined; fileSize?: { min?: number | undefined; max?: number | undefined; } | undefined; customValidation?: string | undefined; additionalRules?: Record<string, any> | undefined; } // =============================== // Conditional Logic and Branching // =============================== /** * Action types for conditional logic */ export enum ConditionalAction { SHOW = 'show', HIDE = 'hide', REQUIRE = 'require', MAKE_OPTIONAL = 'make_optional', SKIP = 'skip', JUMP_TO = 'jump_to', JUMP_TO_PAGE = 'jump_to_page', SUBMIT_FORM = 'submit_form', SET_VALUE = 'set_value', CLEAR_VALUE = 'clear_value', DISABLE = 'disable', ENABLE = 'enable', SHOW_MESSAGE = 'show_message', REDIRECT = 'redirect', } /** * Logic combination operators */ export enum LogicCombinator { AND = 'and', OR = 'or', XOR = 'xor', NAND = 'nand', NOR = 'nor', } /** * Base conditional action interface */ export interface BaseConditionalAction { /** * Type of action to perform */ action: ConditionalAction; /** * Whether this action is enabled */ enabled?: boolean | undefined; /** * Optional delay before executing action (in milliseconds) */ delay?: number | undefined; } /** * Show/hide action for questions */ export interface ShowHideAction extends BaseConditionalAction { action: ConditionalAction.SHOW | ConditionalAction.HIDE; /** * Animation effect for showing/hiding */ animation?: 'fade' | 'slide' | 'none' | undefined; /** * Animation duration in milliseconds */ animationDuration?: number | undefined; } /** * Require/optional action for questions */ export interface RequireAction extends BaseConditionalAction { action: ConditionalAction.REQUIRE | ConditionalAction.MAKE_OPTIONAL; /** * Custom validation message when making required */ validationMessage?: string | undefined; } /** * Jump to question action */ export interface JumpToAction extends BaseConditionalAction { action: ConditionalAction.JUMP_TO; /** * Target question ID to jump to */ targetQuestionId: string; /** * Whether to skip validation of questions before the jump */ skipValidation?: boolean | undefined; } /** * Jump to page action (for multi-page forms) */ export interface JumpToPageAction extends BaseConditionalAction { action: ConditionalAction.JUMP_TO_PAGE; /** * Target page ID or page number */ targetPage: string | number; /** * Whether to skip validation of current page */ skipValidation?: boolean | undefined; } /** * Set value action */ export interface SetValueAction extends BaseConditionalAction { action: ConditionalAction.SET_VALUE; /** * Target question ID to set value for */ targetQuestionId: string; /** * Value to set (type depends on target question type) */ value: any; /** * Whether to trigger validation after setting value */ triggerValidation?: boolean | undefined; } /** * Clear value action */ export interface ClearValueAction extends BaseConditionalAction { action: ConditionalAction.CLEAR_VALUE; /** * Target question ID to clear */ targetQuestionId: string; /** * Whether to trigger validation after clearing */ triggerValidation?: boolean | undefined; } /** * Enable/disable action */ export interface EnableDisableAction extends BaseConditionalAction { action: ConditionalAction.ENABLE | ConditionalAction.DISABLE; /** * Visual state when disabled */ disabledStyle?: 'grayed_out' | 'hidden' | 'readonly' | undefined; } /** * Show message action */ export interface ShowMessageAction extends BaseConditionalAction { action: ConditionalAction.SHOW_MESSAGE; /** * Message to display */ message: string; /** * Message type/style */ messageType?: 'info' | 'warning' | 'error' | 'success' | undefined; /** * How long to show message (in milliseconds, 0 = permanent until condition changes) */ duration?: number | undefined; /** * Position to show message */ position?: 'above' | 'below' | 'inline' | 'popup' | undefined; } /** * Redirect action */ export interface RedirectAction extends BaseConditionalAction { action: ConditionalAction.REDIRECT; /** * URL to redirect to */ url: string; /** * Whether to open in new window/tab */ newWindow?: boolean | undefined; /** * Confirmation message before redirect */ confirmationMessage?: string | undefined; } /** * Submit form action */ export interface SubmitFormAction extends BaseConditionalAction { action: ConditionalAction.SUBMIT_FORM; /** * Whether to validate form before submitting */ validateBeforeSubmit?: boolean | undefined; /** * Custom submission endpoint (overrides form's default) */ customEndpoint?: string | undefined; } /** * Skip action (continue to next question) */ export interface SkipAction extends BaseConditionalAction { action: ConditionalAction.SKIP; /** * Number of questions to skip (default 1) */ skipCount?: number | undefined; } /** * Union type of all conditional actions */ export type ConditionalActionConfig = | ShowHideAction | RequireAction | JumpToAction | JumpToPageAction | SetValueAction | ClearValueAction | EnableDisableAction | ShowMessageAction | RedirectAction | SubmitFormAction | SkipAction; /** * Individual condition within conditional logic */ export interface LogicCondition { /** * Unique identifier for this condition */ id?: string | undefined; /** * ID of the question this condition refers to */ questionId: string; /** * Operator for the condition */ operator: LogicOperator; /** * Value to compare against (type depends on target question type and operator) */ value: any; /** * Whether this condition is case-sensitive (for text comparisons) */ caseSensitive?: boolean | undefined; /** * Whether to negate this condition (NOT) */ negate?: boolean | undefined; /** * Custom error message if condition validation fails */ errorMessage?: string | undefined; } /** * Group of conditions with a combinator */ export interface LogicConditionGroup { /** * Unique identifier for this condition group */ id?: string | undefined; /** * Combinator for this group's conditions */ combinator: LogicCombinator; /** * Conditions in this group */ conditions: LogicCondition[]; /** * Nested condition groups (for complex logic) */ groups?: LogicConditionGroup[] | undefined; /** * Whether to negate the entire group result */ negate?: boolean | undefined; } /** * Enhanced conditional logic interface for form questions * * Defines rules for showing/hiding questions, jumping between sections, * and performing actions based on user responses. Supports complex * branching logic with nested conditions and multiple actions. */ export interface ConditionalLogic { /** * Unique identifier for this logic rule */ id?: string | undefined; /** * Name/title for this logic rule (for management/debugging) */ name?: string | undefined; /** * Description of what this logic does */ description?: string | undefined; /** * Whether this logic rule is enabled */ enabled?: boolean | undefined; /** * Priority/order of execution (lower numbers execute first) */ priority?: number | undefined; /** * Root condition group */ conditionGroup: LogicConditionGroup; /** * Actions to perform when conditions are met */ actions: ConditionalActionConfig[]; /** * Actions to perform when conditions are NOT met (optional) */ elseActions?: ConditionalActionConfig[] | undefined; /** * Whether to re-evaluate this logic when any form value changes */ reEvaluateOnChange?: boolean | undefined; /** * Specific question IDs that trigger re-evaluation (if not reEvaluateOnChange) */ triggerQuestions?: string[] | undefined; /** * Whether this logic should only run once per form session */ runOnce?: boolean | undefined; /** * Metadata for this logic rule */ metadata?: { createdAt?: string | undefined; updatedAt?: string | undefined; createdBy?: string | undefined; version?: number | undefined; tags?: string[] | undefined; } | undefined; } /** * Question-specific settings */ export interface QuestionSettings { /** * For rating questions: minimum rating value */ minRating?: number | undefined; /** * For rating questions: maximum rating value */ maxRating?: number | undefined; /** * For rating questions: rating labels */ ratingLabels?: string[] | undefined; /** * For file upload: allowed file types */ allowedFileTypes?: string[] | undefined; /** * For file upload: maximum file size in MB */ maxFileSize?: number | undefined; /** * For file upload: maximum number of files */ maxFiles?: number | undefined; /** * For choice questions: whether to allow "other" option */ allowOther?: boolean | undefined; /** * For choice questions: maximum selections allowed */ maxSelections?: number | undefined; /** * For text inputs: maximum character count */ maxLength?: number | undefined; /** * For text inputs: minimum character count */ minLength?: number | undefined; /** * For date inputs: minimum date */ minDate?: string | undefined; /** * For date inputs: maximum date */ maxDate?: string | undefined; /** * Custom properties for specific question types */ customProperties?: Record<string, any> | undefined; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/learnwithcc/tally-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server