@forms.expert/sdk 0.1.4 → 0.1.5

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../core/index.ts","../../core/types.ts","../../core/api-client.ts","../../core/forms-sdk.ts"],"sourcesContent":["// Core exports\nexport * from './types';\nexport * from './api-client';\nexport * from './forms-sdk';\n","/**\n * Form field types\n */\nexport type BasicFieldType =\n | 'text' | 'email' | 'number' | 'textarea' | 'select'\n | 'checkbox' | 'file' | 'date' | 'hidden';\n\nexport type InteractiveFieldType =\n | 'radio' | 'multiselect' | 'rating' | 'scale' | 'toggle'\n | 'ranking' | 'imageChoice' | 'phone' | 'url' | 'password'\n | 'richText' | 'slider' | 'currency' | 'time' | 'datetime'\n | 'dateRange' | 'address' | 'name' | 'dropdown' | 'colorPicker'\n | 'location' | 'opinionScale' | 'consent';\n\nexport type LayoutFieldType = 'heading' | 'divider' | 'paragraph';\n\nexport type FormFieldType = BasicFieldType | InteractiveFieldType | LayoutFieldType;\n\nexport interface FormFieldOption {\n label: string;\n value: string;\n imageUrl?: string;\n}\n\n/**\n * Form field definition\n */\nexport interface FormField {\n name: string;\n type: FormFieldType;\n label?: string;\n placeholder?: string;\n required?: boolean;\n options?: string[] | FormFieldOption[];\n defaultValue?: unknown;\n maxFileSize?: number;\n allowedMimeTypes?: string[];\n multiple?: boolean;\n min?: number;\n max?: number;\n step?: number;\n ratingMax?: number;\n lowLabel?: string;\n highLabel?: string;\n defaultCountryCode?: string;\n currencyCode?: string;\n currencySymbol?: string;\n addressFields?: ('street' | 'street2' | 'city' | 'state' | 'zip' | 'country')[];\n nameFields?: ('prefix' | 'first' | 'middle' | 'last' | 'suffix')[];\n content?: string;\n paragraphFontSize?: number;\n consentText?: string;\n consentUrl?: string;\n maxLength?: number;\n stepId?: string;\n visibleWhen?: {\n field: string;\n operator: 'eq' | 'neq' | 'contains' | 'gt' | 'lt';\n value: unknown;\n };\n}\n\n/**\n * Form styling configuration\n */\nexport interface FormStyling {\n theme: 'light' | 'dark' | 'system';\n primaryColor: string;\n backgroundColor: string;\n textColor: string;\n errorColor?: string;\n successColor?: string;\n borderRadius: 'none' | 'sm' | 'md' | 'lg';\n fontSize: 'sm' | 'md' | 'lg';\n buttonStyle: 'filled' | 'outline';\n labelPosition: 'top' | 'left' | 'floating';\n customCss?: string;\n // Extended styling (matching hosted form builder)\n fontFamily?: string;\n formWidth?: 'narrow' | 'medium' | 'wide' | 'full';\n fieldLayout?: 'stacked' | 'inline';\n buttonColor?: string;\n buttonText?: string;\n buttonRadius?: 'none' | 'small' | 'medium' | 'large' | 'full';\n buttonAlign?: 'left' | 'center' | 'right';\n fieldSpacing?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n formPadding?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n labelSpacing?: 'compact' | 'normal' | 'relaxed';\n placeholderFontSize?: 'small' | 'medium' | 'large';\n headingSize?: 'small' | 'medium' | 'large' | 'extra-large';\n paragraphSize?: 'small' | 'medium' | 'large';\n hideRequiredAsterisk?: boolean;\n logoUrl?: string;\n logoPosition?: 'top-left' | 'top-center' | 'top-right';\n coverImageUrl?: string;\n backgroundImageUrl?: string;\n backgroundOverlay?: number;\n transparentBackground?: boolean;\n}\n\n/**\n * Form schema\n */\nexport interface FormSchema {\n fields: FormField[];\n styling?: FormStyling;\n}\n\n/**\n * Form captcha settings\n */\nexport interface CaptchaSettings {\n enabled: boolean;\n provider?: 'turnstile' | 'recaptcha' | 'hcaptcha';\n siteKey?: string;\n}\n\n/**\n * Form branding configuration\n */\nexport interface FormBranding {\n enabled: boolean;\n text?: string;\n url?: string;\n}\n\n/**\n * Form status response from API\n */\nexport interface FormStatusResponse {\n active: boolean;\n formId?: string;\n name?: string;\n mode?: 'free' | 'schema';\n schema?: FormSchema;\n error?: string;\n settings?: {\n captcha: CaptchaSettings;\n honeypot: boolean;\n allowAttachments: boolean;\n maxAttachments?: number;\n maxAttachmentSize?: number;\n successMessage?: string;\n redirectUrl?: string;\n };\n /** Branding configuration */\n branding?: FormBranding;\n /** Available published translation language codes */\n availableLanguages?: string[];\n /** Current language applied to this response (null if base language) */\n currentLanguage?: string | null;\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n field: string;\n message: string;\n}\n\n/**\n * Validation response from API\n */\nexport interface ValidationResponse {\n valid: boolean;\n errors: ValidationError[];\n}\n\n/**\n * Submission response from API\n */\nexport interface SubmissionResponse {\n success: boolean;\n submissionId: string;\n message: string;\n}\n\n/**\n * Submit options\n */\nexport interface SubmitOptions {\n pageUrl?: string;\n captchaToken?: string;\n /** Callback for upload progress */\n onProgress?: (progress: UploadProgress) => void;\n}\n\n/**\n * Upload progress information\n */\nexport interface UploadProgress {\n loaded: number;\n total: number;\n percentage: number;\n}\n\n/**\n * File validation error\n */\nexport interface FileValidationError {\n field: string;\n file: string;\n error: 'size' | 'type' | 'count';\n message: string;\n}\n\n/**\n * SDK configuration\n */\nexport interface FormsSDKConfig {\n apiKey: string;\n resourceId: string;\n baseUrl?: string;\n}\n\n/**\n * Form handler options\n */\nexport interface FormHandlerOptions {\n /** Track form views for analytics (completion rate) */\n trackViews?: boolean;\n onSubmitStart?: () => void;\n onSubmitSuccess?: (response: SubmissionResponse) => void;\n onSubmitError?: (error: FormsError) => void;\n onValidationError?: (errors: ValidationError[]) => void;\n}\n\n/**\n * Forms SDK error\n */\nexport class FormsError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode: number,\n public retryAfter?: number\n ) {\n super(message);\n this.name = 'FormsError';\n }\n}\n\n/**\n * Validation error class\n */\nexport class FormValidationError extends Error {\n constructor(public errors: ValidationError[]) {\n super('Validation failed');\n this.name = 'FormValidationError';\n }\n}\n","import {\n FormsSDKConfig,\n FormStatusResponse,\n ValidationResponse,\n SubmissionResponse,\n SubmitOptions,\n FormsError,\n UploadProgress,\n} from './types';\n\n/**\n * API client for forms backend\n */\nexport class FormsApiClient {\n private baseUrl: string;\n private apiKey: string;\n private resourceId: string;\n\n constructor(config: FormsSDKConfig) {\n this.apiKey = config.apiKey;\n this.resourceId = config.resourceId;\n this.baseUrl = (config.baseUrl || 'https://api.forms.expert/api/v1').replace(/\\/$/, '');\n }\n\n /**\n * Build URL with token query parameter\n */\n private buildUrl(path: string): string {\n const separator = path.includes('?') ? '&' : '?';\n return `${this.baseUrl}${path}${separator}token=${encodeURIComponent(this.apiKey)}`;\n }\n\n /**\n * Make an API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: object\n ): Promise<T> {\n const url = this.buildUrl(path);\n\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n throw new FormsError(\n data.message || 'Request failed',\n data.code || 'UNKNOWN_ERROR',\n response.status,\n data.retryAfter\n );\n }\n\n return data;\n }\n\n /**\n * Check if form is active and get configuration\n */\n async isActive(slug: string, lang?: string): Promise<FormStatusResponse> {\n const langParam = lang ? `?lang=${encodeURIComponent(lang)}` : '';\n return this.request('GET', `/f/${this.resourceId}/${slug}/is-active${langParam}`);\n }\n\n /**\n * Validate form data without submitting\n */\n async validate(\n slug: string,\n data: Record<string, unknown>\n ): Promise<ValidationResponse> {\n return this.request('POST', `/f/${this.resourceId}/${slug}/validate`, {\n data,\n });\n }\n\n /**\n * Submit form data (supports files)\n */\n async submit(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n const url = this.buildUrl(`/f/${this.resourceId}/${slug}`);\n \n // Check if data contains files\n const hasFiles = Object.values(data).some(\n (v) => v instanceof File || (v instanceof FileList && v.length > 0)\n );\n\n if (hasFiles || options?.onProgress) {\n // Use FormData and XMLHttpRequest for file uploads with progress\n return this.submitWithFormData(url, data, options);\n }\n\n // Use regular JSON request for non-file submissions\n return this.request('POST', `/f/${this.resourceId}/${slug}`, {\n data,\n pageUrl: options?.pageUrl || (typeof window !== 'undefined' ? window.location.href : undefined),\n captchaToken: options?.captchaToken,\n });\n }\n\n /**\n * Submit with FormData (for file uploads with progress tracking)\n */\n private submitWithFormData(\n url: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n return new Promise((resolve, reject) => {\n const formData = new FormData();\n\n // Add data fields\n for (const [key, value] of Object.entries(data)) {\n if (value instanceof File) {\n formData.append(key, value);\n } else if (value instanceof FileList) {\n Array.from(value).forEach((file) => formData.append(key, file));\n } else if (value !== undefined && value !== null) {\n formData.append(`data[${key}]`, String(value));\n }\n }\n\n // Add metadata\n const pageUrl = options?.pageUrl || (typeof window !== 'undefined' ? window.location.href : '');\n if (pageUrl) {\n formData.append('pageUrl', pageUrl);\n }\n if (options?.captchaToken) {\n formData.append('captchaToken', options.captchaToken);\n }\n\n const xhr = new XMLHttpRequest();\n\n // Progress tracking\n if (options?.onProgress) {\n xhr.upload.addEventListener('progress', (event) => {\n if (event.lengthComputable) {\n options.onProgress!({\n loaded: event.loaded,\n total: event.total,\n percentage: Math.round((event.loaded / event.total) * 100),\n });\n }\n });\n }\n\n xhr.addEventListener('load', () => {\n try {\n const response = JSON.parse(xhr.responseText);\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(response);\n } else {\n reject(new FormsError(\n response.message || 'Submission failed',\n response.code || 'UNKNOWN_ERROR',\n xhr.status,\n response.retryAfter\n ));\n }\n } catch {\n reject(new FormsError('Invalid response', 'PARSE_ERROR', xhr.status));\n }\n });\n\n xhr.addEventListener('error', () => {\n reject(new FormsError('Network error', 'NETWORK_ERROR', 0));\n });\n\n xhr.addEventListener('abort', () => {\n reject(new FormsError('Request aborted', 'ABORTED', 0));\n });\n\n xhr.open('POST', url);\n xhr.send(formData);\n });\n }\n\n /**\n * Track a form view (for analytics completion rate)\n */\n async trackView(slug: string): Promise<void> {\n const url = this.buildUrl(`/f/${this.resourceId}/${slug}/view`);\n await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } }).catch(() => {});\n }\n\n /**\n * Get resource ID\n */\n getResourceId(): string {\n return this.resourceId;\n }\n\n /**\n * Get base URL\n */\n getBaseUrl(): string {\n return this.baseUrl;\n }\n}\n","import { FormsApiClient } from './api-client';\nimport {\n FormsSDKConfig,\n FormStatusResponse,\n ValidationResponse,\n SubmissionResponse,\n SubmitOptions,\n FormHandlerOptions,\n FormsError,\n FormValidationError,\n} from './types';\n\n/**\n * Form handler for a specific form\n */\nexport class FormHandler {\n private apiClient: FormsApiClient;\n private slug: string;\n private config: FormStatusResponse | null = null;\n private options: FormHandlerOptions;\n\n constructor(\n apiClient: FormsApiClient,\n slug: string,\n options: FormHandlerOptions = {}\n ) {\n this.apiClient = apiClient;\n this.slug = slug;\n this.options = options;\n }\n\n /**\n * Initialize form handler and fetch configuration\n */\n async initialize(lang?: string): Promise<FormStatusResponse> {\n this.config = await this.apiClient.isActive(this.slug, lang);\n if (this.options.trackViews) {\n this.apiClient.trackView(this.slug);\n }\n return this.config;\n }\n\n /**\n * Get cached form configuration\n */\n getConfig(): FormStatusResponse | null {\n return this.config;\n }\n\n /**\n * Check if form is active\n */\n isActive(): boolean {\n return this.config?.active ?? false;\n }\n\n /**\n * Check if captcha is required\n */\n requiresCaptcha(): boolean {\n return this.config?.settings?.captcha?.enabled ?? false;\n }\n\n /**\n * Get captcha provider\n */\n getCaptchaProvider(): 'turnstile' | 'recaptcha' | 'hcaptcha' | undefined {\n return this.config?.settings?.captcha?.provider;\n }\n\n /**\n * Get form schema\n */\n getSchema() {\n return this.config?.schema;\n }\n\n /**\n * Validate form data\n */\n async validate(data: Record<string, unknown>): Promise<ValidationResponse> {\n return this.apiClient.validate(this.slug, data);\n }\n\n /**\n * Submit form data\n */\n async submit(\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n this.options.onSubmitStart?.();\n\n try {\n // Validate first if in schema mode\n if (this.config?.mode === 'schema') {\n const validation = await this.validate(data);\n if (!validation.valid) {\n this.options.onValidationError?.(validation.errors);\n throw new FormValidationError(validation.errors);\n }\n }\n\n const response = await this.apiClient.submit(this.slug, data, options);\n this.options.onSubmitSuccess?.(response);\n return response;\n } catch (error) {\n if (error instanceof FormsError) {\n this.options.onSubmitError?.(error);\n }\n throw error;\n }\n }\n\n /**\n * Get success message from config\n */\n getSuccessMessage(): string {\n return this.config?.settings?.successMessage || 'Form submitted successfully!';\n }\n\n /**\n * Get redirect URL from config\n */\n getRedirectUrl(): string | undefined {\n return this.config?.settings?.redirectUrl;\n }\n}\n\n/**\n * Main Forms SDK class\n */\nexport class FormsSDK {\n private apiClient: FormsApiClient;\n\n constructor(config: FormsSDKConfig) {\n this.apiClient = new FormsApiClient(config);\n }\n\n /**\n * Check if form is active and get configuration\n */\n async isActive(slug: string, lang?: string): Promise<FormStatusResponse> {\n return this.apiClient.isActive(slug, lang);\n }\n\n /**\n * Validate form data without submitting\n */\n async validate(\n slug: string,\n data: Record<string, unknown>\n ): Promise<ValidationResponse> {\n return this.apiClient.validate(slug, data);\n }\n\n /**\n * Submit form data\n */\n async submit(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n return this.apiClient.submit(slug, data, options);\n }\n\n /**\n * Create a form handler for a specific form\n */\n form(slug: string, options?: FormHandlerOptions): FormHandler {\n return new FormHandler(this.apiClient, slug, options);\n }\n\n /**\n * Track a form view (for analytics completion rate)\n */\n async trackView(slug: string): Promise<void> {\n return this.apiClient.trackView(slug);\n }\n\n /**\n * Submit with retry logic for rate limits\n */\n async submitWithRetry(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions & { maxRetries?: number }\n ): Promise<SubmissionResponse> {\n const maxRetries = options?.maxRetries ?? 3;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await this.submit(slug, data, options);\n } catch (error) {\n lastError = error as Error;\n\n if (error instanceof FormsError) {\n // Don't retry validation or auth errors\n if (\n [\n 'VALIDATION_ERROR',\n 'CAPTCHA_REQUIRED',\n 'ORIGIN_NOT_ALLOWED',\n ].includes(error.code)\n ) {\n throw error;\n }\n\n // Retry rate limits with backoff\n if (error.code.includes('RATE_LIMIT')) {\n const retryAfter = error.retryAfter || Math.pow(2, attempt) * 1000;\n await new Promise((resolve) => setTimeout(resolve, retryAfter));\n continue;\n }\n }\n\n // Exponential backoff for network errors\n await new Promise((resolve) =>\n setTimeout(resolve, Math.pow(2, attempt) * 1000)\n );\n }\n }\n\n throw lastError!;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACuOO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACE,SACO,MACA,YACA,YACP;AACA,UAAM,OAAO;AAJN;AACA;AACA;AAGP,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YAAmB,QAA2B;AAC5C,UAAM,mBAAmB;AADR;AAEjB,SAAK,OAAO;AAAA,EACd;AACF;;;AC9OO,IAAM,iBAAN,MAAqB;AAAA,EAK1B,YAAY,QAAwB;AAClC,SAAK,SAAS,OAAO;AACrB,SAAK,aAAa,OAAO;AACzB,SAAK,WAAW,OAAO,WAAW,mCAAmC,QAAQ,OAAO,EAAE;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAS,MAAsB;AACrC,UAAM,YAAY,KAAK,SAAS,GAAG,IAAI,MAAM;AAC7C,WAAO,GAAG,KAAK,OAAO,GAAG,IAAI,GAAG,SAAS,SAAS,mBAAmB,KAAK,MAAM,CAAC;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,QACZ,QACA,MACA,MACY;AACZ,UAAM,MAAM,KAAK,SAAS,IAAI;AAE9B,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC;AAAA,MACA,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IACtC,CAAC;AAED,UAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,KAAK,WAAW;AAAA,QAChB,KAAK,QAAQ;AAAA,QACb,SAAS;AAAA,QACT,KAAK;AAAA,MACP;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,MAAc,MAA4C;AACvE,UAAM,YAAY,OAAO,SAAS,mBAAmB,IAAI,CAAC,KAAK;AAC/D,WAAO,KAAK,QAAQ,OAAO,MAAM,KAAK,UAAU,IAAI,IAAI,aAAa,SAAS,EAAE;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SACJ,MACA,MAC6B;AAC7B,WAAO,KAAK,QAAQ,QAAQ,MAAM,KAAK,UAAU,IAAI,IAAI,aAAa;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OACJ,MACA,MACA,SAC6B;AAC7B,UAAM,MAAM,KAAK,SAAS,MAAM,KAAK,UAAU,IAAI,IAAI,EAAE;AAGzD,UAAM,WAAW,OAAO,OAAO,IAAI,EAAE;AAAA,MACnC,CAAC,MAAM,aAAa,QAAS,aAAa,YAAY,EAAE,SAAS;AAAA,IACnE;AAEA,QAAI,YAAY,SAAS,YAAY;AAEnC,aAAO,KAAK,mBAAmB,KAAK,MAAM,OAAO;AAAA,IACnD;AAGA,WAAO,KAAK,QAAQ,QAAQ,MAAM,KAAK,UAAU,IAAI,IAAI,IAAI;AAAA,MAC3D;AAAA,MACA,SAAS,SAAS,YAAY,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO;AAAA,MACrF,cAAc,SAAS;AAAA,IACzB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,mBACN,KACA,MACA,SAC6B;AAC7B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,WAAW,IAAI,SAAS;AAG9B,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,YAAI,iBAAiB,MAAM;AACzB,mBAAS,OAAO,KAAK,KAAK;AAAA,QAC5B,WAAW,iBAAiB,UAAU;AACpC,gBAAM,KAAK,KAAK,EAAE,QAAQ,CAAC,SAAS,SAAS,OAAO,KAAK,IAAI,CAAC;AAAA,QAChE,WAAW,UAAU,UAAa,UAAU,MAAM;AAChD,mBAAS,OAAO,QAAQ,GAAG,KAAK,OAAO,KAAK,CAAC;AAAA,QAC/C;AAAA,MACF;AAGA,YAAM,UAAU,SAAS,YAAY,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO;AAC5F,UAAI,SAAS;AACX,iBAAS,OAAO,WAAW,OAAO;AAAA,MACpC;AACA,UAAI,SAAS,cAAc;AACzB,iBAAS,OAAO,gBAAgB,QAAQ,YAAY;AAAA,MACtD;AAEA,YAAM,MAAM,IAAI,eAAe;AAG/B,UAAI,SAAS,YAAY;AACvB,YAAI,OAAO,iBAAiB,YAAY,CAAC,UAAU;AACjD,cAAI,MAAM,kBAAkB;AAC1B,oBAAQ,WAAY;AAAA,cAClB,QAAQ,MAAM;AAAA,cACd,OAAO,MAAM;AAAA,cACb,YAAY,KAAK,MAAO,MAAM,SAAS,MAAM,QAAS,GAAG;AAAA,YAC3D,CAAC;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH;AAEA,UAAI,iBAAiB,QAAQ,MAAM;AACjC,YAAI;AACF,gBAAM,WAAW,KAAK,MAAM,IAAI,YAAY;AAC5C,cAAI,IAAI,UAAU,OAAO,IAAI,SAAS,KAAK;AACzC,oBAAQ,QAAQ;AAAA,UAClB,OAAO;AACL,mBAAO,IAAI;AAAA,cACT,SAAS,WAAW;AAAA,cACpB,SAAS,QAAQ;AAAA,cACjB,IAAI;AAAA,cACJ,SAAS;AAAA,YACX,CAAC;AAAA,UACH;AAAA,QACF,QAAQ;AACN,iBAAO,IAAI,WAAW,oBAAoB,eAAe,IAAI,MAAM,CAAC;AAAA,QACtE;AAAA,MACF,CAAC;AAED,UAAI,iBAAiB,SAAS,MAAM;AAClC,eAAO,IAAI,WAAW,iBAAiB,iBAAiB,CAAC,CAAC;AAAA,MAC5D,CAAC;AAED,UAAI,iBAAiB,SAAS,MAAM;AAClC,eAAO,IAAI,WAAW,mBAAmB,WAAW,CAAC,CAAC;AAAA,MACxD,CAAC;AAED,UAAI,KAAK,QAAQ,GAAG;AACpB,UAAI,KAAK,QAAQ;AAAA,IACnB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,MAA6B;AAC3C,UAAM,MAAM,KAAK,SAAS,MAAM,KAAK,UAAU,IAAI,IAAI,OAAO;AAC9D,UAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,SAAS,EAAE,gBAAgB,mBAAmB,EAAE,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACtG;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,aAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AACF;;;ACnMO,IAAM,cAAN,MAAkB;AAAA,EAMvB,YACE,WACA,MACA,UAA8B,CAAC,GAC/B;AAPF,SAAQ,SAAoC;AAQ1C,SAAK,YAAY;AACjB,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,MAA4C;AAC3D,SAAK,SAAS,MAAM,KAAK,UAAU,SAAS,KAAK,MAAM,IAAI;AAC3D,QAAI,KAAK,QAAQ,YAAY;AAC3B,WAAK,UAAU,UAAU,KAAK,IAAI;AAAA,IACpC;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,YAAuC;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,WAAoB;AAClB,WAAO,KAAK,QAAQ,UAAU;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA2B;AACzB,WAAO,KAAK,QAAQ,UAAU,SAAS,WAAW;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAyE;AACvE,WAAO,KAAK,QAAQ,UAAU,SAAS;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY;AACV,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,MAA4D;AACzE,WAAO,KAAK,UAAU,SAAS,KAAK,MAAM,IAAI;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OACJ,MACA,SAC6B;AAC7B,SAAK,QAAQ,gBAAgB;AAE7B,QAAI;AAEF,UAAI,KAAK,QAAQ,SAAS,UAAU;AAClC,cAAM,aAAa,MAAM,KAAK,SAAS,IAAI;AAC3C,YAAI,CAAC,WAAW,OAAO;AACrB,eAAK,QAAQ,oBAAoB,WAAW,MAAM;AAClD,gBAAM,IAAI,oBAAoB,WAAW,MAAM;AAAA,QACjD;AAAA,MACF;AAEA,YAAM,WAAW,MAAM,KAAK,UAAU,OAAO,KAAK,MAAM,MAAM,OAAO;AACrE,WAAK,QAAQ,kBAAkB,QAAQ;AACvC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,iBAAiB,YAAY;AAC/B,aAAK,QAAQ,gBAAgB,KAAK;AAAA,MACpC;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA4B;AAC1B,WAAO,KAAK,QAAQ,UAAU,kBAAkB;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAqC;AACnC,WAAO,KAAK,QAAQ,UAAU;AAAA,EAChC;AACF;AAKO,IAAM,WAAN,MAAe;AAAA,EAGpB,YAAY,QAAwB;AAClC,SAAK,YAAY,IAAI,eAAe,MAAM;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,MAAc,MAA4C;AACvE,WAAO,KAAK,UAAU,SAAS,MAAM,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SACJ,MACA,MAC6B;AAC7B,WAAO,KAAK,UAAU,SAAS,MAAM,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OACJ,MACA,MACA,SAC6B;AAC7B,WAAO,KAAK,UAAU,OAAO,MAAM,MAAM,OAAO;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,MAAc,SAA2C;AAC5D,WAAO,IAAI,YAAY,KAAK,WAAW,MAAM,OAAO;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,MAA6B;AAC3C,WAAO,KAAK,UAAU,UAAU,IAAI;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBACJ,MACA,MACA,SAC6B;AAC7B,UAAM,aAAa,SAAS,cAAc;AAC1C,QAAI,YAA0B;AAE9B,aAAS,UAAU,GAAG,UAAU,YAAY,WAAW;AACrD,UAAI;AACF,eAAO,MAAM,KAAK,OAAO,MAAM,MAAM,OAAO;AAAA,MAC9C,SAAS,OAAO;AACd,oBAAY;AAEZ,YAAI,iBAAiB,YAAY;AAE/B,cACE;AAAA,YACE;AAAA,YACA;AAAA,YACA;AAAA,UACF,EAAE,SAAS,MAAM,IAAI,GACrB;AACA,kBAAM;AAAA,UACR;AAGA,cAAI,MAAM,KAAK,SAAS,YAAY,GAAG;AACrC,kBAAM,aAAa,MAAM,cAAc,KAAK,IAAI,GAAG,OAAO,IAAI;AAC9D,kBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,UAAU,CAAC;AAC9D;AAAA,UACF;AAAA,QACF;AAGA,cAAM,IAAI;AAAA,UAAQ,CAAC,YACjB,WAAW,SAAS,KAAK,IAAI,GAAG,OAAO,IAAI,GAAI;AAAA,QACjD;AAAA,MACF;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACF;","names":[]}
1
+ {"version":3,"sources":["../../core/index.ts","../../core/types.ts","../../core/api-client.ts","../../core/forms-sdk.ts"],"sourcesContent":["// Core exports\nexport * from './types';\nexport * from './api-client';\nexport * from './forms-sdk';\n","/**\n * Form field types\n */\nexport type BasicFieldType =\n | 'text' | 'email' | 'number' | 'textarea' | 'select'\n | 'checkbox' | 'file' | 'date' | 'hidden';\n\nexport type InteractiveFieldType =\n | 'radio' | 'multiselect' | 'rating' | 'scale' | 'toggle'\n | 'ranking' | 'imageChoice' | 'phone' | 'url' | 'password'\n | 'richText' | 'slider' | 'currency' | 'time' | 'datetime'\n | 'dateRange' | 'address' | 'name' | 'dropdown' | 'colorPicker'\n | 'location' | 'opinionScale' | 'consent';\n\nexport type LayoutFieldType = 'heading' | 'divider' | 'paragraph';\n\nexport type FormFieldType = BasicFieldType | InteractiveFieldType | LayoutFieldType;\n\nexport interface FormFieldOption {\n label: string;\n value: string;\n imageUrl?: string;\n}\n\n/**\n * Form field definition\n */\nexport interface FormField {\n name: string;\n type: FormFieldType;\n label?: string;\n placeholder?: string;\n required?: boolean;\n options?: string[] | FormFieldOption[];\n defaultValue?: unknown;\n maxFileSize?: number;\n allowedMimeTypes?: string[];\n multiple?: boolean;\n min?: number;\n max?: number;\n step?: number;\n ratingMax?: number;\n lowLabel?: string;\n highLabel?: string;\n defaultCountryCode?: string;\n currencyCode?: string;\n currencySymbol?: string;\n addressFields?: ('street' | 'street2' | 'city' | 'state' | 'zip' | 'country')[];\n nameFields?: ('prefix' | 'first' | 'middle' | 'last' | 'suffix')[];\n content?: string;\n paragraphFontSize?: number;\n consentText?: string;\n consentUrl?: string;\n maxLength?: number;\n stepId?: string;\n visibleWhen?: {\n field: string;\n operator: 'eq' | 'neq' | 'contains' | 'gt' | 'lt';\n value: unknown;\n };\n}\n\n/**\n * Form styling configuration\n */\nexport interface FormStyling {\n theme: 'light' | 'dark' | 'system';\n primaryColor: string;\n backgroundColor: string;\n textColor: string;\n errorColor?: string;\n successColor?: string;\n borderRadius: 'none' | 'sm' | 'md' | 'lg';\n fontSize: 'sm' | 'md' | 'lg';\n buttonStyle: 'filled' | 'outline';\n labelPosition: 'top' | 'left' | 'floating';\n customCss?: string;\n // Extended styling (matching hosted form builder)\n fontFamily?: string;\n formWidth?: 'narrow' | 'medium' | 'wide' | 'full';\n fieldLayout?: 'stacked' | 'inline';\n buttonColor?: string;\n buttonText?: string;\n buttonRadius?: 'none' | 'small' | 'medium' | 'large' | 'full';\n buttonAlign?: 'left' | 'center' | 'right';\n fieldSpacing?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n formPadding?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n labelSpacing?: 'compact' | 'normal' | 'relaxed';\n placeholderFontSize?: 'small' | 'medium' | 'large';\n headingSize?: 'small' | 'medium' | 'large' | 'extra-large';\n paragraphSize?: 'small' | 'medium' | 'large';\n hideRequiredAsterisk?: boolean;\n logoUrl?: string;\n logoPosition?: 'top-left' | 'top-center' | 'top-right';\n coverImageUrl?: string;\n backgroundImageUrl?: string;\n backgroundOverlay?: number;\n transparentBackground?: boolean;\n}\n\n/**\n * Form schema\n */\nexport interface FormSchema {\n fields: FormField[];\n styling?: FormStyling;\n}\n\n/**\n * Form captcha settings\n */\nexport interface CaptchaSettings {\n enabled: boolean;\n provider?: 'turnstile' | 'recaptcha' | 'hcaptcha';\n siteKey?: string;\n}\n\n/**\n * Form branding configuration\n */\nexport interface FormBranding {\n enabled: boolean;\n text?: string;\n url?: string;\n}\n\n/**\n * Form status response from API\n */\nexport interface FormStatusResponse {\n active: boolean;\n formId?: string;\n name?: string;\n description?: string | null;\n mode?: 'free' | 'schema';\n type?: 'hosted' | 'embed' | 'both';\n layout?: 'single' | 'multi-step';\n schema?: FormSchema;\n steps?: Array<{ title: string; description?: string; fields: string[] }>;\n /** Top-level styling (full hosted styling config) */\n styling?: FormStyling;\n embedConfig?: {\n width?: string;\n height?: string;\n maxHeight?: string;\n minHeight?: string;\n autoResize?: boolean;\n transparentBackground?: boolean;\n [key: string]: unknown;\n };\n error?: string;\n /** Captcha settings (top-level) */\n captcha?: CaptchaSettings;\n settings?: {\n /** @deprecated Use top-level captcha instead */\n captcha?: CaptchaSettings;\n honeypot: boolean;\n allowAttachments: boolean;\n maxAttachments?: number;\n maxAttachmentSize?: number;\n successMessage?: string;\n redirectUrl?: string;\n };\n /** Access control configuration */\n accessControl?: {\n mode: string;\n passwordProtected: boolean;\n };\n /** Branding configuration */\n branding?: FormBranding;\n closesAt?: string | null;\n resourceId?: string;\n /** Available published translation language codes */\n availableLanguages?: string[];\n /** Current language applied to this response (null if base language) */\n currentLanguage?: string | null;\n /** Whether to show language switch UI */\n showLanguageSwitch?: boolean;\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n field: string;\n message: string;\n}\n\n/**\n * Validation response from API\n */\nexport interface ValidationResponse {\n valid: boolean;\n errors: ValidationError[];\n}\n\n/**\n * Submission response from API\n */\nexport interface SubmissionResponse {\n success: boolean;\n submissionId: string;\n message: string;\n}\n\n/**\n * Submit options\n */\nexport interface SubmitOptions {\n pageUrl?: string;\n captchaToken?: string;\n /** Callback for upload progress */\n onProgress?: (progress: UploadProgress) => void;\n}\n\n/**\n * Upload progress information\n */\nexport interface UploadProgress {\n loaded: number;\n total: number;\n percentage: number;\n}\n\n/**\n * File validation error\n */\nexport interface FileValidationError {\n field: string;\n file: string;\n error: 'size' | 'type' | 'count';\n message: string;\n}\n\n/**\n * SDK configuration\n */\nexport interface FormsSDKConfig {\n apiKey: string;\n resourceId: string;\n baseUrl?: string;\n}\n\n/**\n * Form handler options\n */\nexport interface FormHandlerOptions {\n /** Track form views for analytics (completion rate) */\n trackViews?: boolean;\n onSubmitStart?: () => void;\n onSubmitSuccess?: (response: SubmissionResponse) => void;\n onSubmitError?: (error: FormsError) => void;\n onValidationError?: (errors: ValidationError[]) => void;\n}\n\n/**\n * Forms SDK error\n */\nexport class FormsError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode: number,\n public retryAfter?: number\n ) {\n super(message);\n this.name = 'FormsError';\n }\n}\n\n/**\n * Validation error class\n */\nexport class FormValidationError extends Error {\n constructor(public errors: ValidationError[]) {\n super('Validation failed');\n this.name = 'FormValidationError';\n }\n}\n","import {\n FormsSDKConfig,\n FormStatusResponse,\n ValidationResponse,\n SubmissionResponse,\n SubmitOptions,\n FormsError,\n UploadProgress,\n} from './types';\n\n/**\n * API client for forms backend\n */\nexport class FormsApiClient {\n private baseUrl: string;\n private apiKey: string;\n private resourceId: string;\n\n constructor(config: FormsSDKConfig) {\n this.apiKey = config.apiKey;\n this.resourceId = config.resourceId;\n this.baseUrl = (config.baseUrl || 'https://api.forms.expert/api/v1').replace(/\\/$/, '');\n }\n\n /**\n * Build URL with token query parameter\n */\n private buildUrl(path: string): string {\n const separator = path.includes('?') ? '&' : '?';\n return `${this.baseUrl}${path}${separator}token=${encodeURIComponent(this.apiKey)}`;\n }\n\n /**\n * Make an API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: object\n ): Promise<T> {\n const url = this.buildUrl(path);\n\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n throw new FormsError(\n data.message || 'Request failed',\n data.code || 'UNKNOWN_ERROR',\n response.status,\n data.retryAfter\n );\n }\n\n return data;\n }\n\n /**\n * Check if form is active and get configuration\n */\n async isActive(slug: string, lang?: string): Promise<FormStatusResponse> {\n const langParam = lang ? `?lang=${encodeURIComponent(lang)}` : '';\n return this.request('GET', `/f/${this.resourceId}/${slug}/is-active${langParam}`);\n }\n\n /**\n * Validate form data without submitting\n */\n async validate(\n slug: string,\n data: Record<string, unknown>\n ): Promise<ValidationResponse> {\n return this.request('POST', `/f/${this.resourceId}/${slug}/validate`, {\n data,\n });\n }\n\n /**\n * Submit form data (supports files)\n */\n async submit(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n const url = this.buildUrl(`/f/${this.resourceId}/${slug}`);\n \n // Check if data contains files\n const hasFiles = Object.values(data).some(\n (v) => v instanceof File || (v instanceof FileList && v.length > 0)\n );\n\n if (hasFiles || options?.onProgress) {\n // Use FormData and XMLHttpRequest for file uploads with progress\n return this.submitWithFormData(url, data, options);\n }\n\n // Use regular JSON request for non-file submissions\n return this.request('POST', `/f/${this.resourceId}/${slug}`, {\n data,\n pageUrl: options?.pageUrl || (typeof window !== 'undefined' ? window.location.href : undefined),\n captchaToken: options?.captchaToken,\n });\n }\n\n /**\n * Submit with FormData (for file uploads with progress tracking)\n */\n private submitWithFormData(\n url: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n return new Promise((resolve, reject) => {\n const formData = new FormData();\n\n // Add data fields\n for (const [key, value] of Object.entries(data)) {\n if (value instanceof File) {\n formData.append(key, value);\n } else if (value instanceof FileList) {\n Array.from(value).forEach((file) => formData.append(key, file));\n } else if (value !== undefined && value !== null) {\n formData.append(`data[${key}]`, String(value));\n }\n }\n\n // Add metadata\n const pageUrl = options?.pageUrl || (typeof window !== 'undefined' ? window.location.href : '');\n if (pageUrl) {\n formData.append('pageUrl', pageUrl);\n }\n if (options?.captchaToken) {\n formData.append('captchaToken', options.captchaToken);\n }\n\n const xhr = new XMLHttpRequest();\n\n // Progress tracking\n if (options?.onProgress) {\n xhr.upload.addEventListener('progress', (event) => {\n if (event.lengthComputable) {\n options.onProgress!({\n loaded: event.loaded,\n total: event.total,\n percentage: Math.round((event.loaded / event.total) * 100),\n });\n }\n });\n }\n\n xhr.addEventListener('load', () => {\n try {\n const response = JSON.parse(xhr.responseText);\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(response);\n } else {\n reject(new FormsError(\n response.message || 'Submission failed',\n response.code || 'UNKNOWN_ERROR',\n xhr.status,\n response.retryAfter\n ));\n }\n } catch {\n reject(new FormsError('Invalid response', 'PARSE_ERROR', xhr.status));\n }\n });\n\n xhr.addEventListener('error', () => {\n reject(new FormsError('Network error', 'NETWORK_ERROR', 0));\n });\n\n xhr.addEventListener('abort', () => {\n reject(new FormsError('Request aborted', 'ABORTED', 0));\n });\n\n xhr.open('POST', url);\n xhr.send(formData);\n });\n }\n\n /**\n * Track a form view (for analytics completion rate)\n */\n async trackView(slug: string): Promise<void> {\n const url = this.buildUrl(`/f/${this.resourceId}/${slug}/view`);\n await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } }).catch(() => {});\n }\n\n /**\n * Get resource ID\n */\n getResourceId(): string {\n return this.resourceId;\n }\n\n /**\n * Get base URL\n */\n getBaseUrl(): string {\n return this.baseUrl;\n }\n}\n","import { FormsApiClient } from './api-client';\nimport {\n FormsSDKConfig,\n FormStatusResponse,\n ValidationResponse,\n SubmissionResponse,\n SubmitOptions,\n FormHandlerOptions,\n FormsError,\n FormValidationError,\n} from './types';\n\n/**\n * Form handler for a specific form\n */\nexport class FormHandler {\n private apiClient: FormsApiClient;\n private slug: string;\n private config: FormStatusResponse | null = null;\n private options: FormHandlerOptions;\n\n constructor(\n apiClient: FormsApiClient,\n slug: string,\n options: FormHandlerOptions = {}\n ) {\n this.apiClient = apiClient;\n this.slug = slug;\n this.options = options;\n }\n\n /**\n * Initialize form handler and fetch configuration\n */\n async initialize(lang?: string): Promise<FormStatusResponse> {\n this.config = await this.apiClient.isActive(this.slug, lang);\n if (this.options.trackViews) {\n this.apiClient.trackView(this.slug);\n }\n return this.config;\n }\n\n /**\n * Get cached form configuration\n */\n getConfig(): FormStatusResponse | null {\n return this.config;\n }\n\n /**\n * Check if form is active\n */\n isActive(): boolean {\n return this.config?.active ?? false;\n }\n\n /**\n * Check if captcha is required\n */\n requiresCaptcha(): boolean {\n return this.config?.settings?.captcha?.enabled ?? false;\n }\n\n /**\n * Get captcha provider\n */\n getCaptchaProvider(): 'turnstile' | 'recaptcha' | 'hcaptcha' | undefined {\n return this.config?.settings?.captcha?.provider;\n }\n\n /**\n * Get form schema\n */\n getSchema() {\n return this.config?.schema;\n }\n\n /**\n * Validate form data\n */\n async validate(data: Record<string, unknown>): Promise<ValidationResponse> {\n return this.apiClient.validate(this.slug, data);\n }\n\n /**\n * Submit form data\n */\n async submit(\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n this.options.onSubmitStart?.();\n\n try {\n // Validate first if in schema mode\n if (this.config?.mode === 'schema') {\n const validation = await this.validate(data);\n if (!validation.valid) {\n this.options.onValidationError?.(validation.errors);\n throw new FormValidationError(validation.errors);\n }\n }\n\n const response = await this.apiClient.submit(this.slug, data, options);\n this.options.onSubmitSuccess?.(response);\n return response;\n } catch (error) {\n if (error instanceof FormsError) {\n this.options.onSubmitError?.(error);\n }\n throw error;\n }\n }\n\n /**\n * Get success message from config\n */\n getSuccessMessage(): string {\n return this.config?.settings?.successMessage || 'Form submitted successfully!';\n }\n\n /**\n * Get redirect URL from config\n */\n getRedirectUrl(): string | undefined {\n return this.config?.settings?.redirectUrl;\n }\n}\n\n/**\n * Main Forms SDK class\n */\nexport class FormsSDK {\n private apiClient: FormsApiClient;\n\n constructor(config: FormsSDKConfig) {\n this.apiClient = new FormsApiClient(config);\n }\n\n /**\n * Check if form is active and get configuration\n */\n async isActive(slug: string, lang?: string): Promise<FormStatusResponse> {\n return this.apiClient.isActive(slug, lang);\n }\n\n /**\n * Validate form data without submitting\n */\n async validate(\n slug: string,\n data: Record<string, unknown>\n ): Promise<ValidationResponse> {\n return this.apiClient.validate(slug, data);\n }\n\n /**\n * Submit form data\n */\n async submit(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n return this.apiClient.submit(slug, data, options);\n }\n\n /**\n * Create a form handler for a specific form\n */\n form(slug: string, options?: FormHandlerOptions): FormHandler {\n return new FormHandler(this.apiClient, slug, options);\n }\n\n /**\n * Track a form view (for analytics completion rate)\n */\n async trackView(slug: string): Promise<void> {\n return this.apiClient.trackView(slug);\n }\n\n /**\n * Submit with retry logic for rate limits\n */\n async submitWithRetry(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions & { maxRetries?: number }\n ): Promise<SubmissionResponse> {\n const maxRetries = options?.maxRetries ?? 3;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await this.submit(slug, data, options);\n } catch (error) {\n lastError = error as Error;\n\n if (error instanceof FormsError) {\n // Don't retry validation or auth errors\n if (\n [\n 'VALIDATION_ERROR',\n 'CAPTCHA_REQUIRED',\n 'ORIGIN_NOT_ALLOWED',\n ].includes(error.code)\n ) {\n throw error;\n }\n\n // Retry rate limits with backoff\n if (error.code.includes('RATE_LIMIT')) {\n const retryAfter = error.retryAfter || Math.pow(2, attempt) * 1000;\n await new Promise((resolve) => setTimeout(resolve, retryAfter));\n continue;\n }\n }\n\n // Exponential backoff for network errors\n await new Promise((resolve) =>\n setTimeout(resolve, Math.pow(2, attempt) * 1000)\n );\n }\n }\n\n throw lastError!;\n }\n}\n"],"mappings":"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","names":[]}
@@ -113,11 +113,33 @@ interface FormStatusResponse {
113
113
  active: boolean;
114
114
  formId?: string;
115
115
  name?: string;
116
+ description?: string | null;
116
117
  mode?: 'free' | 'schema';
118
+ type?: 'hosted' | 'embed' | 'both';
119
+ layout?: 'single' | 'multi-step';
117
120
  schema?: FormSchema;
121
+ steps?: Array<{
122
+ title: string;
123
+ description?: string;
124
+ fields: string[];
125
+ }>;
126
+ /** Top-level styling (full hosted styling config) */
127
+ styling?: FormStyling;
128
+ embedConfig?: {
129
+ width?: string;
130
+ height?: string;
131
+ maxHeight?: string;
132
+ minHeight?: string;
133
+ autoResize?: boolean;
134
+ transparentBackground?: boolean;
135
+ [key: string]: unknown;
136
+ };
118
137
  error?: string;
138
+ /** Captcha settings (top-level) */
139
+ captcha?: CaptchaSettings;
119
140
  settings?: {
120
- captcha: CaptchaSettings;
141
+ /** @deprecated Use top-level captcha instead */
142
+ captcha?: CaptchaSettings;
121
143
  honeypot: boolean;
122
144
  allowAttachments: boolean;
123
145
  maxAttachments?: number;
@@ -125,12 +147,21 @@ interface FormStatusResponse {
125
147
  successMessage?: string;
126
148
  redirectUrl?: string;
127
149
  };
150
+ /** Access control configuration */
151
+ accessControl?: {
152
+ mode: string;
153
+ passwordProtected: boolean;
154
+ };
128
155
  /** Branding configuration */
129
156
  branding?: FormBranding;
157
+ closesAt?: string | null;
158
+ resourceId?: string;
130
159
  /** Available published translation language codes */
131
160
  availableLanguages?: string[];
132
161
  /** Current language applied to this response (null if base language) */
133
162
  currentLanguage?: string | null;
163
+ /** Whether to show language switch UI */
164
+ showLanguageSwitch?: boolean;
134
165
  }
135
166
  /**
136
167
  * Validation error
@@ -113,11 +113,33 @@ interface FormStatusResponse {
113
113
  active: boolean;
114
114
  formId?: string;
115
115
  name?: string;
116
+ description?: string | null;
116
117
  mode?: 'free' | 'schema';
118
+ type?: 'hosted' | 'embed' | 'both';
119
+ layout?: 'single' | 'multi-step';
117
120
  schema?: FormSchema;
121
+ steps?: Array<{
122
+ title: string;
123
+ description?: string;
124
+ fields: string[];
125
+ }>;
126
+ /** Top-level styling (full hosted styling config) */
127
+ styling?: FormStyling;
128
+ embedConfig?: {
129
+ width?: string;
130
+ height?: string;
131
+ maxHeight?: string;
132
+ minHeight?: string;
133
+ autoResize?: boolean;
134
+ transparentBackground?: boolean;
135
+ [key: string]: unknown;
136
+ };
118
137
  error?: string;
138
+ /** Captcha settings (top-level) */
139
+ captcha?: CaptchaSettings;
119
140
  settings?: {
120
- captcha: CaptchaSettings;
141
+ /** @deprecated Use top-level captcha instead */
142
+ captcha?: CaptchaSettings;
121
143
  honeypot: boolean;
122
144
  allowAttachments: boolean;
123
145
  maxAttachments?: number;
@@ -125,12 +147,21 @@ interface FormStatusResponse {
125
147
  successMessage?: string;
126
148
  redirectUrl?: string;
127
149
  };
150
+ /** Access control configuration */
151
+ accessControl?: {
152
+ mode: string;
153
+ passwordProtected: boolean;
154
+ };
128
155
  /** Branding configuration */
129
156
  branding?: FormBranding;
157
+ closesAt?: string | null;
158
+ resourceId?: string;
130
159
  /** Available published translation language codes */
131
160
  availableLanguages?: string[];
132
161
  /** Current language applied to this response (null if base language) */
133
162
  currentLanguage?: string | null;
163
+ /** Whether to show language switch UI */
164
+ showLanguageSwitch?: boolean;
134
165
  }
135
166
  /**
136
167
  * Validation error
@@ -1 +1 @@
1
- {"version":3,"sources":["../../core/types.ts","../../core/api-client.ts","../../core/forms-sdk.ts"],"sourcesContent":["/**\n * Form field types\n */\nexport type BasicFieldType =\n | 'text' | 'email' | 'number' | 'textarea' | 'select'\n | 'checkbox' | 'file' | 'date' | 'hidden';\n\nexport type InteractiveFieldType =\n | 'radio' | 'multiselect' | 'rating' | 'scale' | 'toggle'\n | 'ranking' | 'imageChoice' | 'phone' | 'url' | 'password'\n | 'richText' | 'slider' | 'currency' | 'time' | 'datetime'\n | 'dateRange' | 'address' | 'name' | 'dropdown' | 'colorPicker'\n | 'location' | 'opinionScale' | 'consent';\n\nexport type LayoutFieldType = 'heading' | 'divider' | 'paragraph';\n\nexport type FormFieldType = BasicFieldType | InteractiveFieldType | LayoutFieldType;\n\nexport interface FormFieldOption {\n label: string;\n value: string;\n imageUrl?: string;\n}\n\n/**\n * Form field definition\n */\nexport interface FormField {\n name: string;\n type: FormFieldType;\n label?: string;\n placeholder?: string;\n required?: boolean;\n options?: string[] | FormFieldOption[];\n defaultValue?: unknown;\n maxFileSize?: number;\n allowedMimeTypes?: string[];\n multiple?: boolean;\n min?: number;\n max?: number;\n step?: number;\n ratingMax?: number;\n lowLabel?: string;\n highLabel?: string;\n defaultCountryCode?: string;\n currencyCode?: string;\n currencySymbol?: string;\n addressFields?: ('street' | 'street2' | 'city' | 'state' | 'zip' | 'country')[];\n nameFields?: ('prefix' | 'first' | 'middle' | 'last' | 'suffix')[];\n content?: string;\n paragraphFontSize?: number;\n consentText?: string;\n consentUrl?: string;\n maxLength?: number;\n stepId?: string;\n visibleWhen?: {\n field: string;\n operator: 'eq' | 'neq' | 'contains' | 'gt' | 'lt';\n value: unknown;\n };\n}\n\n/**\n * Form styling configuration\n */\nexport interface FormStyling {\n theme: 'light' | 'dark' | 'system';\n primaryColor: string;\n backgroundColor: string;\n textColor: string;\n errorColor?: string;\n successColor?: string;\n borderRadius: 'none' | 'sm' | 'md' | 'lg';\n fontSize: 'sm' | 'md' | 'lg';\n buttonStyle: 'filled' | 'outline';\n labelPosition: 'top' | 'left' | 'floating';\n customCss?: string;\n // Extended styling (matching hosted form builder)\n fontFamily?: string;\n formWidth?: 'narrow' | 'medium' | 'wide' | 'full';\n fieldLayout?: 'stacked' | 'inline';\n buttonColor?: string;\n buttonText?: string;\n buttonRadius?: 'none' | 'small' | 'medium' | 'large' | 'full';\n buttonAlign?: 'left' | 'center' | 'right';\n fieldSpacing?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n formPadding?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n labelSpacing?: 'compact' | 'normal' | 'relaxed';\n placeholderFontSize?: 'small' | 'medium' | 'large';\n headingSize?: 'small' | 'medium' | 'large' | 'extra-large';\n paragraphSize?: 'small' | 'medium' | 'large';\n hideRequiredAsterisk?: boolean;\n logoUrl?: string;\n logoPosition?: 'top-left' | 'top-center' | 'top-right';\n coverImageUrl?: string;\n backgroundImageUrl?: string;\n backgroundOverlay?: number;\n transparentBackground?: boolean;\n}\n\n/**\n * Form schema\n */\nexport interface FormSchema {\n fields: FormField[];\n styling?: FormStyling;\n}\n\n/**\n * Form captcha settings\n */\nexport interface CaptchaSettings {\n enabled: boolean;\n provider?: 'turnstile' | 'recaptcha' | 'hcaptcha';\n siteKey?: string;\n}\n\n/**\n * Form branding configuration\n */\nexport interface FormBranding {\n enabled: boolean;\n text?: string;\n url?: string;\n}\n\n/**\n * Form status response from API\n */\nexport interface FormStatusResponse {\n active: boolean;\n formId?: string;\n name?: string;\n mode?: 'free' | 'schema';\n schema?: FormSchema;\n error?: string;\n settings?: {\n captcha: CaptchaSettings;\n honeypot: boolean;\n allowAttachments: boolean;\n maxAttachments?: number;\n maxAttachmentSize?: number;\n successMessage?: string;\n redirectUrl?: string;\n };\n /** Branding configuration */\n branding?: FormBranding;\n /** Available published translation language codes */\n availableLanguages?: string[];\n /** Current language applied to this response (null if base language) */\n currentLanguage?: string | null;\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n field: string;\n message: string;\n}\n\n/**\n * Validation response from API\n */\nexport interface ValidationResponse {\n valid: boolean;\n errors: ValidationError[];\n}\n\n/**\n * Submission response from API\n */\nexport interface SubmissionResponse {\n success: boolean;\n submissionId: string;\n message: string;\n}\n\n/**\n * Submit options\n */\nexport interface SubmitOptions {\n pageUrl?: string;\n captchaToken?: string;\n /** Callback for upload progress */\n onProgress?: (progress: UploadProgress) => void;\n}\n\n/**\n * Upload progress information\n */\nexport interface UploadProgress {\n loaded: number;\n total: number;\n percentage: number;\n}\n\n/**\n * File validation error\n */\nexport interface FileValidationError {\n field: string;\n file: string;\n error: 'size' | 'type' | 'count';\n message: string;\n}\n\n/**\n * SDK configuration\n */\nexport interface FormsSDKConfig {\n apiKey: string;\n resourceId: string;\n baseUrl?: string;\n}\n\n/**\n * Form handler options\n */\nexport interface FormHandlerOptions {\n /** Track form views for analytics (completion rate) */\n trackViews?: boolean;\n onSubmitStart?: () => void;\n onSubmitSuccess?: (response: SubmissionResponse) => void;\n onSubmitError?: (error: FormsError) => void;\n onValidationError?: (errors: ValidationError[]) => void;\n}\n\n/**\n * Forms SDK error\n */\nexport class FormsError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode: number,\n public retryAfter?: number\n ) {\n super(message);\n this.name = 'FormsError';\n }\n}\n\n/**\n * Validation error class\n */\nexport class FormValidationError extends Error {\n constructor(public errors: ValidationError[]) {\n super('Validation failed');\n this.name = 'FormValidationError';\n }\n}\n","import {\n FormsSDKConfig,\n FormStatusResponse,\n ValidationResponse,\n SubmissionResponse,\n SubmitOptions,\n FormsError,\n UploadProgress,\n} from './types';\n\n/**\n * API client for forms backend\n */\nexport class FormsApiClient {\n private baseUrl: string;\n private apiKey: string;\n private resourceId: string;\n\n constructor(config: FormsSDKConfig) {\n this.apiKey = config.apiKey;\n this.resourceId = config.resourceId;\n this.baseUrl = (config.baseUrl || 'https://api.forms.expert/api/v1').replace(/\\/$/, '');\n }\n\n /**\n * Build URL with token query parameter\n */\n private buildUrl(path: string): string {\n const separator = path.includes('?') ? '&' : '?';\n return `${this.baseUrl}${path}${separator}token=${encodeURIComponent(this.apiKey)}`;\n }\n\n /**\n * Make an API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: object\n ): Promise<T> {\n const url = this.buildUrl(path);\n\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n throw new FormsError(\n data.message || 'Request failed',\n data.code || 'UNKNOWN_ERROR',\n response.status,\n data.retryAfter\n );\n }\n\n return data;\n }\n\n /**\n * Check if form is active and get configuration\n */\n async isActive(slug: string, lang?: string): Promise<FormStatusResponse> {\n const langParam = lang ? `?lang=${encodeURIComponent(lang)}` : '';\n return this.request('GET', `/f/${this.resourceId}/${slug}/is-active${langParam}`);\n }\n\n /**\n * Validate form data without submitting\n */\n async validate(\n slug: string,\n data: Record<string, unknown>\n ): Promise<ValidationResponse> {\n return this.request('POST', `/f/${this.resourceId}/${slug}/validate`, {\n data,\n });\n }\n\n /**\n * Submit form data (supports files)\n */\n async submit(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n const url = this.buildUrl(`/f/${this.resourceId}/${slug}`);\n \n // Check if data contains files\n const hasFiles = Object.values(data).some(\n (v) => v instanceof File || (v instanceof FileList && v.length > 0)\n );\n\n if (hasFiles || options?.onProgress) {\n // Use FormData and XMLHttpRequest for file uploads with progress\n return this.submitWithFormData(url, data, options);\n }\n\n // Use regular JSON request for non-file submissions\n return this.request('POST', `/f/${this.resourceId}/${slug}`, {\n data,\n pageUrl: options?.pageUrl || (typeof window !== 'undefined' ? window.location.href : undefined),\n captchaToken: options?.captchaToken,\n });\n }\n\n /**\n * Submit with FormData (for file uploads with progress tracking)\n */\n private submitWithFormData(\n url: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n return new Promise((resolve, reject) => {\n const formData = new FormData();\n\n // Add data fields\n for (const [key, value] of Object.entries(data)) {\n if (value instanceof File) {\n formData.append(key, value);\n } else if (value instanceof FileList) {\n Array.from(value).forEach((file) => formData.append(key, file));\n } else if (value !== undefined && value !== null) {\n formData.append(`data[${key}]`, String(value));\n }\n }\n\n // Add metadata\n const pageUrl = options?.pageUrl || (typeof window !== 'undefined' ? window.location.href : '');\n if (pageUrl) {\n formData.append('pageUrl', pageUrl);\n }\n if (options?.captchaToken) {\n formData.append('captchaToken', options.captchaToken);\n }\n\n const xhr = new XMLHttpRequest();\n\n // Progress tracking\n if (options?.onProgress) {\n xhr.upload.addEventListener('progress', (event) => {\n if (event.lengthComputable) {\n options.onProgress!({\n loaded: event.loaded,\n total: event.total,\n percentage: Math.round((event.loaded / event.total) * 100),\n });\n }\n });\n }\n\n xhr.addEventListener('load', () => {\n try {\n const response = JSON.parse(xhr.responseText);\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(response);\n } else {\n reject(new FormsError(\n response.message || 'Submission failed',\n response.code || 'UNKNOWN_ERROR',\n xhr.status,\n response.retryAfter\n ));\n }\n } catch {\n reject(new FormsError('Invalid response', 'PARSE_ERROR', xhr.status));\n }\n });\n\n xhr.addEventListener('error', () => {\n reject(new FormsError('Network error', 'NETWORK_ERROR', 0));\n });\n\n xhr.addEventListener('abort', () => {\n reject(new FormsError('Request aborted', 'ABORTED', 0));\n });\n\n xhr.open('POST', url);\n xhr.send(formData);\n });\n }\n\n /**\n * Track a form view (for analytics completion rate)\n */\n async trackView(slug: string): Promise<void> {\n const url = this.buildUrl(`/f/${this.resourceId}/${slug}/view`);\n await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } }).catch(() => {});\n }\n\n /**\n * Get resource ID\n */\n getResourceId(): string {\n return this.resourceId;\n }\n\n /**\n * Get base URL\n */\n getBaseUrl(): string {\n return this.baseUrl;\n }\n}\n","import { FormsApiClient } from './api-client';\nimport {\n FormsSDKConfig,\n FormStatusResponse,\n ValidationResponse,\n SubmissionResponse,\n SubmitOptions,\n FormHandlerOptions,\n FormsError,\n FormValidationError,\n} from './types';\n\n/**\n * Form handler for a specific form\n */\nexport class FormHandler {\n private apiClient: FormsApiClient;\n private slug: string;\n private config: FormStatusResponse | null = null;\n private options: FormHandlerOptions;\n\n constructor(\n apiClient: FormsApiClient,\n slug: string,\n options: FormHandlerOptions = {}\n ) {\n this.apiClient = apiClient;\n this.slug = slug;\n this.options = options;\n }\n\n /**\n * Initialize form handler and fetch configuration\n */\n async initialize(lang?: string): Promise<FormStatusResponse> {\n this.config = await this.apiClient.isActive(this.slug, lang);\n if (this.options.trackViews) {\n this.apiClient.trackView(this.slug);\n }\n return this.config;\n }\n\n /**\n * Get cached form configuration\n */\n getConfig(): FormStatusResponse | null {\n return this.config;\n }\n\n /**\n * Check if form is active\n */\n isActive(): boolean {\n return this.config?.active ?? false;\n }\n\n /**\n * Check if captcha is required\n */\n requiresCaptcha(): boolean {\n return this.config?.settings?.captcha?.enabled ?? false;\n }\n\n /**\n * Get captcha provider\n */\n getCaptchaProvider(): 'turnstile' | 'recaptcha' | 'hcaptcha' | undefined {\n return this.config?.settings?.captcha?.provider;\n }\n\n /**\n * Get form schema\n */\n getSchema() {\n return this.config?.schema;\n }\n\n /**\n * Validate form data\n */\n async validate(data: Record<string, unknown>): Promise<ValidationResponse> {\n return this.apiClient.validate(this.slug, data);\n }\n\n /**\n * Submit form data\n */\n async submit(\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n this.options.onSubmitStart?.();\n\n try {\n // Validate first if in schema mode\n if (this.config?.mode === 'schema') {\n const validation = await this.validate(data);\n if (!validation.valid) {\n this.options.onValidationError?.(validation.errors);\n throw new FormValidationError(validation.errors);\n }\n }\n\n const response = await this.apiClient.submit(this.slug, data, options);\n this.options.onSubmitSuccess?.(response);\n return response;\n } catch (error) {\n if (error instanceof FormsError) {\n this.options.onSubmitError?.(error);\n }\n throw error;\n }\n }\n\n /**\n * Get success message from config\n */\n getSuccessMessage(): string {\n return this.config?.settings?.successMessage || 'Form submitted successfully!';\n }\n\n /**\n * Get redirect URL from config\n */\n getRedirectUrl(): string | undefined {\n return this.config?.settings?.redirectUrl;\n }\n}\n\n/**\n * Main Forms SDK class\n */\nexport class FormsSDK {\n private apiClient: FormsApiClient;\n\n constructor(config: FormsSDKConfig) {\n this.apiClient = new FormsApiClient(config);\n }\n\n /**\n * Check if form is active and get configuration\n */\n async isActive(slug: string, lang?: string): Promise<FormStatusResponse> {\n return this.apiClient.isActive(slug, lang);\n }\n\n /**\n * Validate form data without submitting\n */\n async validate(\n slug: string,\n data: Record<string, unknown>\n ): Promise<ValidationResponse> {\n return this.apiClient.validate(slug, data);\n }\n\n /**\n * Submit form data\n */\n async submit(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n return this.apiClient.submit(slug, data, options);\n }\n\n /**\n * Create a form handler for a specific form\n */\n form(slug: string, options?: FormHandlerOptions): FormHandler {\n return new FormHandler(this.apiClient, slug, options);\n }\n\n /**\n * Track a form view (for analytics completion rate)\n */\n async trackView(slug: string): Promise<void> {\n return this.apiClient.trackView(slug);\n }\n\n /**\n * Submit with retry logic for rate limits\n */\n async submitWithRetry(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions & { maxRetries?: number }\n ): Promise<SubmissionResponse> {\n const maxRetries = options?.maxRetries ?? 3;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await this.submit(slug, data, options);\n } catch (error) {\n lastError = error as Error;\n\n if (error instanceof FormsError) {\n // Don't retry validation or auth errors\n if (\n [\n 'VALIDATION_ERROR',\n 'CAPTCHA_REQUIRED',\n 'ORIGIN_NOT_ALLOWED',\n ].includes(error.code)\n ) {\n throw error;\n }\n\n // Retry rate limits with backoff\n if (error.code.includes('RATE_LIMIT')) {\n const retryAfter = error.retryAfter || Math.pow(2, attempt) * 1000;\n await new Promise((resolve) => setTimeout(resolve, retryAfter));\n continue;\n }\n }\n\n // Exponential backoff for network errors\n await new Promise((resolve) =>\n setTimeout(resolve, Math.pow(2, attempt) * 1000)\n );\n }\n }\n\n throw lastError!;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../core/types.ts","../../core/api-client.ts","../../core/forms-sdk.ts"],"sourcesContent":["/**\n * Form field types\n */\nexport type BasicFieldType =\n | 'text' | 'email' | 'number' | 'textarea' | 'select'\n | 'checkbox' | 'file' | 'date' | 'hidden';\n\nexport type InteractiveFieldType =\n | 'radio' | 'multiselect' | 'rating' | 'scale' | 'toggle'\n | 'ranking' | 'imageChoice' | 'phone' | 'url' | 'password'\n | 'richText' | 'slider' | 'currency' | 'time' | 'datetime'\n | 'dateRange' | 'address' | 'name' | 'dropdown' | 'colorPicker'\n | 'location' | 'opinionScale' | 'consent';\n\nexport type LayoutFieldType = 'heading' | 'divider' | 'paragraph';\n\nexport type FormFieldType = BasicFieldType | InteractiveFieldType | LayoutFieldType;\n\nexport interface FormFieldOption {\n label: string;\n value: string;\n imageUrl?: string;\n}\n\n/**\n * Form field definition\n */\nexport interface FormField {\n name: string;\n type: FormFieldType;\n label?: string;\n placeholder?: string;\n required?: boolean;\n options?: string[] | FormFieldOption[];\n defaultValue?: unknown;\n maxFileSize?: number;\n allowedMimeTypes?: string[];\n multiple?: boolean;\n min?: number;\n max?: number;\n step?: number;\n ratingMax?: number;\n lowLabel?: string;\n highLabel?: string;\n defaultCountryCode?: string;\n currencyCode?: string;\n currencySymbol?: string;\n addressFields?: ('street' | 'street2' | 'city' | 'state' | 'zip' | 'country')[];\n nameFields?: ('prefix' | 'first' | 'middle' | 'last' | 'suffix')[];\n content?: string;\n paragraphFontSize?: number;\n consentText?: string;\n consentUrl?: string;\n maxLength?: number;\n stepId?: string;\n visibleWhen?: {\n field: string;\n operator: 'eq' | 'neq' | 'contains' | 'gt' | 'lt';\n value: unknown;\n };\n}\n\n/**\n * Form styling configuration\n */\nexport interface FormStyling {\n theme: 'light' | 'dark' | 'system';\n primaryColor: string;\n backgroundColor: string;\n textColor: string;\n errorColor?: string;\n successColor?: string;\n borderRadius: 'none' | 'sm' | 'md' | 'lg';\n fontSize: 'sm' | 'md' | 'lg';\n buttonStyle: 'filled' | 'outline';\n labelPosition: 'top' | 'left' | 'floating';\n customCss?: string;\n // Extended styling (matching hosted form builder)\n fontFamily?: string;\n formWidth?: 'narrow' | 'medium' | 'wide' | 'full';\n fieldLayout?: 'stacked' | 'inline';\n buttonColor?: string;\n buttonText?: string;\n buttonRadius?: 'none' | 'small' | 'medium' | 'large' | 'full';\n buttonAlign?: 'left' | 'center' | 'right';\n fieldSpacing?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n formPadding?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n labelSpacing?: 'compact' | 'normal' | 'relaxed';\n placeholderFontSize?: 'small' | 'medium' | 'large';\n headingSize?: 'small' | 'medium' | 'large' | 'extra-large';\n paragraphSize?: 'small' | 'medium' | 'large';\n hideRequiredAsterisk?: boolean;\n logoUrl?: string;\n logoPosition?: 'top-left' | 'top-center' | 'top-right';\n coverImageUrl?: string;\n backgroundImageUrl?: string;\n backgroundOverlay?: number;\n transparentBackground?: boolean;\n}\n\n/**\n * Form schema\n */\nexport interface FormSchema {\n fields: FormField[];\n styling?: FormStyling;\n}\n\n/**\n * Form captcha settings\n */\nexport interface CaptchaSettings {\n enabled: boolean;\n provider?: 'turnstile' | 'recaptcha' | 'hcaptcha';\n siteKey?: string;\n}\n\n/**\n * Form branding configuration\n */\nexport interface FormBranding {\n enabled: boolean;\n text?: string;\n url?: string;\n}\n\n/**\n * Form status response from API\n */\nexport interface FormStatusResponse {\n active: boolean;\n formId?: string;\n name?: string;\n description?: string | null;\n mode?: 'free' | 'schema';\n type?: 'hosted' | 'embed' | 'both';\n layout?: 'single' | 'multi-step';\n schema?: FormSchema;\n steps?: Array<{ title: string; description?: string; fields: string[] }>;\n /** Top-level styling (full hosted styling config) */\n styling?: FormStyling;\n embedConfig?: {\n width?: string;\n height?: string;\n maxHeight?: string;\n minHeight?: string;\n autoResize?: boolean;\n transparentBackground?: boolean;\n [key: string]: unknown;\n };\n error?: string;\n /** Captcha settings (top-level) */\n captcha?: CaptchaSettings;\n settings?: {\n /** @deprecated Use top-level captcha instead */\n captcha?: CaptchaSettings;\n honeypot: boolean;\n allowAttachments: boolean;\n maxAttachments?: number;\n maxAttachmentSize?: number;\n successMessage?: string;\n redirectUrl?: string;\n };\n /** Access control configuration */\n accessControl?: {\n mode: string;\n passwordProtected: boolean;\n };\n /** Branding configuration */\n branding?: FormBranding;\n closesAt?: string | null;\n resourceId?: string;\n /** Available published translation language codes */\n availableLanguages?: string[];\n /** Current language applied to this response (null if base language) */\n currentLanguage?: string | null;\n /** Whether to show language switch UI */\n showLanguageSwitch?: boolean;\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n field: string;\n message: string;\n}\n\n/**\n * Validation response from API\n */\nexport interface ValidationResponse {\n valid: boolean;\n errors: ValidationError[];\n}\n\n/**\n * Submission response from API\n */\nexport interface SubmissionResponse {\n success: boolean;\n submissionId: string;\n message: string;\n}\n\n/**\n * Submit options\n */\nexport interface SubmitOptions {\n pageUrl?: string;\n captchaToken?: string;\n /** Callback for upload progress */\n onProgress?: (progress: UploadProgress) => void;\n}\n\n/**\n * Upload progress information\n */\nexport interface UploadProgress {\n loaded: number;\n total: number;\n percentage: number;\n}\n\n/**\n * File validation error\n */\nexport interface FileValidationError {\n field: string;\n file: string;\n error: 'size' | 'type' | 'count';\n message: string;\n}\n\n/**\n * SDK configuration\n */\nexport interface FormsSDKConfig {\n apiKey: string;\n resourceId: string;\n baseUrl?: string;\n}\n\n/**\n * Form handler options\n */\nexport interface FormHandlerOptions {\n /** Track form views for analytics (completion rate) */\n trackViews?: boolean;\n onSubmitStart?: () => void;\n onSubmitSuccess?: (response: SubmissionResponse) => void;\n onSubmitError?: (error: FormsError) => void;\n onValidationError?: (errors: ValidationError[]) => void;\n}\n\n/**\n * Forms SDK error\n */\nexport class FormsError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode: number,\n public retryAfter?: number\n ) {\n super(message);\n this.name = 'FormsError';\n }\n}\n\n/**\n * Validation error class\n */\nexport class FormValidationError extends Error {\n constructor(public errors: ValidationError[]) {\n super('Validation failed');\n this.name = 'FormValidationError';\n }\n}\n","import {\n FormsSDKConfig,\n FormStatusResponse,\n ValidationResponse,\n SubmissionResponse,\n SubmitOptions,\n FormsError,\n UploadProgress,\n} from './types';\n\n/**\n * API client for forms backend\n */\nexport class FormsApiClient {\n private baseUrl: string;\n private apiKey: string;\n private resourceId: string;\n\n constructor(config: FormsSDKConfig) {\n this.apiKey = config.apiKey;\n this.resourceId = config.resourceId;\n this.baseUrl = (config.baseUrl || 'https://api.forms.expert/api/v1').replace(/\\/$/, '');\n }\n\n /**\n * Build URL with token query parameter\n */\n private buildUrl(path: string): string {\n const separator = path.includes('?') ? '&' : '?';\n return `${this.baseUrl}${path}${separator}token=${encodeURIComponent(this.apiKey)}`;\n }\n\n /**\n * Make an API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: object\n ): Promise<T> {\n const url = this.buildUrl(path);\n\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n throw new FormsError(\n data.message || 'Request failed',\n data.code || 'UNKNOWN_ERROR',\n response.status,\n data.retryAfter\n );\n }\n\n return data;\n }\n\n /**\n * Check if form is active and get configuration\n */\n async isActive(slug: string, lang?: string): Promise<FormStatusResponse> {\n const langParam = lang ? `?lang=${encodeURIComponent(lang)}` : '';\n return this.request('GET', `/f/${this.resourceId}/${slug}/is-active${langParam}`);\n }\n\n /**\n * Validate form data without submitting\n */\n async validate(\n slug: string,\n data: Record<string, unknown>\n ): Promise<ValidationResponse> {\n return this.request('POST', `/f/${this.resourceId}/${slug}/validate`, {\n data,\n });\n }\n\n /**\n * Submit form data (supports files)\n */\n async submit(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n const url = this.buildUrl(`/f/${this.resourceId}/${slug}`);\n \n // Check if data contains files\n const hasFiles = Object.values(data).some(\n (v) => v instanceof File || (v instanceof FileList && v.length > 0)\n );\n\n if (hasFiles || options?.onProgress) {\n // Use FormData and XMLHttpRequest for file uploads with progress\n return this.submitWithFormData(url, data, options);\n }\n\n // Use regular JSON request for non-file submissions\n return this.request('POST', `/f/${this.resourceId}/${slug}`, {\n data,\n pageUrl: options?.pageUrl || (typeof window !== 'undefined' ? window.location.href : undefined),\n captchaToken: options?.captchaToken,\n });\n }\n\n /**\n * Submit with FormData (for file uploads with progress tracking)\n */\n private submitWithFormData(\n url: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n return new Promise((resolve, reject) => {\n const formData = new FormData();\n\n // Add data fields\n for (const [key, value] of Object.entries(data)) {\n if (value instanceof File) {\n formData.append(key, value);\n } else if (value instanceof FileList) {\n Array.from(value).forEach((file) => formData.append(key, file));\n } else if (value !== undefined && value !== null) {\n formData.append(`data[${key}]`, String(value));\n }\n }\n\n // Add metadata\n const pageUrl = options?.pageUrl || (typeof window !== 'undefined' ? window.location.href : '');\n if (pageUrl) {\n formData.append('pageUrl', pageUrl);\n }\n if (options?.captchaToken) {\n formData.append('captchaToken', options.captchaToken);\n }\n\n const xhr = new XMLHttpRequest();\n\n // Progress tracking\n if (options?.onProgress) {\n xhr.upload.addEventListener('progress', (event) => {\n if (event.lengthComputable) {\n options.onProgress!({\n loaded: event.loaded,\n total: event.total,\n percentage: Math.round((event.loaded / event.total) * 100),\n });\n }\n });\n }\n\n xhr.addEventListener('load', () => {\n try {\n const response = JSON.parse(xhr.responseText);\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve(response);\n } else {\n reject(new FormsError(\n response.message || 'Submission failed',\n response.code || 'UNKNOWN_ERROR',\n xhr.status,\n response.retryAfter\n ));\n }\n } catch {\n reject(new FormsError('Invalid response', 'PARSE_ERROR', xhr.status));\n }\n });\n\n xhr.addEventListener('error', () => {\n reject(new FormsError('Network error', 'NETWORK_ERROR', 0));\n });\n\n xhr.addEventListener('abort', () => {\n reject(new FormsError('Request aborted', 'ABORTED', 0));\n });\n\n xhr.open('POST', url);\n xhr.send(formData);\n });\n }\n\n /**\n * Track a form view (for analytics completion rate)\n */\n async trackView(slug: string): Promise<void> {\n const url = this.buildUrl(`/f/${this.resourceId}/${slug}/view`);\n await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } }).catch(() => {});\n }\n\n /**\n * Get resource ID\n */\n getResourceId(): string {\n return this.resourceId;\n }\n\n /**\n * Get base URL\n */\n getBaseUrl(): string {\n return this.baseUrl;\n }\n}\n","import { FormsApiClient } from './api-client';\nimport {\n FormsSDKConfig,\n FormStatusResponse,\n ValidationResponse,\n SubmissionResponse,\n SubmitOptions,\n FormHandlerOptions,\n FormsError,\n FormValidationError,\n} from './types';\n\n/**\n * Form handler for a specific form\n */\nexport class FormHandler {\n private apiClient: FormsApiClient;\n private slug: string;\n private config: FormStatusResponse | null = null;\n private options: FormHandlerOptions;\n\n constructor(\n apiClient: FormsApiClient,\n slug: string,\n options: FormHandlerOptions = {}\n ) {\n this.apiClient = apiClient;\n this.slug = slug;\n this.options = options;\n }\n\n /**\n * Initialize form handler and fetch configuration\n */\n async initialize(lang?: string): Promise<FormStatusResponse> {\n this.config = await this.apiClient.isActive(this.slug, lang);\n if (this.options.trackViews) {\n this.apiClient.trackView(this.slug);\n }\n return this.config;\n }\n\n /**\n * Get cached form configuration\n */\n getConfig(): FormStatusResponse | null {\n return this.config;\n }\n\n /**\n * Check if form is active\n */\n isActive(): boolean {\n return this.config?.active ?? false;\n }\n\n /**\n * Check if captcha is required\n */\n requiresCaptcha(): boolean {\n return this.config?.settings?.captcha?.enabled ?? false;\n }\n\n /**\n * Get captcha provider\n */\n getCaptchaProvider(): 'turnstile' | 'recaptcha' | 'hcaptcha' | undefined {\n return this.config?.settings?.captcha?.provider;\n }\n\n /**\n * Get form schema\n */\n getSchema() {\n return this.config?.schema;\n }\n\n /**\n * Validate form data\n */\n async validate(data: Record<string, unknown>): Promise<ValidationResponse> {\n return this.apiClient.validate(this.slug, data);\n }\n\n /**\n * Submit form data\n */\n async submit(\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n this.options.onSubmitStart?.();\n\n try {\n // Validate first if in schema mode\n if (this.config?.mode === 'schema') {\n const validation = await this.validate(data);\n if (!validation.valid) {\n this.options.onValidationError?.(validation.errors);\n throw new FormValidationError(validation.errors);\n }\n }\n\n const response = await this.apiClient.submit(this.slug, data, options);\n this.options.onSubmitSuccess?.(response);\n return response;\n } catch (error) {\n if (error instanceof FormsError) {\n this.options.onSubmitError?.(error);\n }\n throw error;\n }\n }\n\n /**\n * Get success message from config\n */\n getSuccessMessage(): string {\n return this.config?.settings?.successMessage || 'Form submitted successfully!';\n }\n\n /**\n * Get redirect URL from config\n */\n getRedirectUrl(): string | undefined {\n return this.config?.settings?.redirectUrl;\n }\n}\n\n/**\n * Main Forms SDK class\n */\nexport class FormsSDK {\n private apiClient: FormsApiClient;\n\n constructor(config: FormsSDKConfig) {\n this.apiClient = new FormsApiClient(config);\n }\n\n /**\n * Check if form is active and get configuration\n */\n async isActive(slug: string, lang?: string): Promise<FormStatusResponse> {\n return this.apiClient.isActive(slug, lang);\n }\n\n /**\n * Validate form data without submitting\n */\n async validate(\n slug: string,\n data: Record<string, unknown>\n ): Promise<ValidationResponse> {\n return this.apiClient.validate(slug, data);\n }\n\n /**\n * Submit form data\n */\n async submit(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions\n ): Promise<SubmissionResponse> {\n return this.apiClient.submit(slug, data, options);\n }\n\n /**\n * Create a form handler for a specific form\n */\n form(slug: string, options?: FormHandlerOptions): FormHandler {\n return new FormHandler(this.apiClient, slug, options);\n }\n\n /**\n * Track a form view (for analytics completion rate)\n */\n async trackView(slug: string): Promise<void> {\n return this.apiClient.trackView(slug);\n }\n\n /**\n * Submit with retry logic for rate limits\n */\n async submitWithRetry(\n slug: string,\n data: Record<string, unknown>,\n options?: SubmitOptions & { maxRetries?: number }\n ): Promise<SubmissionResponse> {\n const maxRetries = options?.maxRetries ?? 3;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await this.submit(slug, data, options);\n } catch (error) {\n lastError = error as Error;\n\n if (error instanceof FormsError) {\n // Don't retry validation or auth errors\n if (\n [\n 'VALIDATION_ERROR',\n 'CAPTCHA_REQUIRED',\n 'ORIGIN_NOT_ALLOWED',\n ].includes(error.code)\n ) {\n throw error;\n }\n\n // Retry rate limits with backoff\n if (error.code.includes('RATE_LIMIT')) {\n const retryAfter = error.retryAfter || Math.pow(2, attempt) * 1000;\n await new Promise((resolve) => setTimeout(resolve, retryAfter));\n continue;\n }\n }\n\n // Exponential backoff for network errors\n await new Promise((resolve) =>\n setTimeout(resolve, Math.pow(2, attempt) * 1000)\n );\n }\n }\n\n throw lastError!;\n }\n}\n"],"mappings":"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","names":[]}
@@ -539,9 +539,9 @@ function useForm(options) {
539
539
  submit,
540
540
  reset,
541
541
  clearErrors,
542
- requiresCaptcha: config?.settings?.captcha?.enabled ?? false,
543
- captchaProvider: config?.settings?.captcha?.provider,
544
- captchaSiteKey: config?.settings?.captcha?.siteKey,
542
+ requiresCaptcha: config?.captcha?.enabled ?? config?.settings?.captcha?.enabled ?? false,
543
+ captchaProvider: config?.captcha?.provider ?? config?.settings?.captcha?.provider,
544
+ captchaSiteKey: config?.captcha?.siteKey ?? config?.settings?.captcha?.siteKey,
545
545
  honeypotEnabled: config?.settings?.honeypot ?? false,
546
546
  allowsAttachments,
547
547
  maxAttachments,
@@ -795,7 +795,7 @@ function FormsExpertForm({
795
795
  }
796
796
  }
797
797
  };
798
- const styling = form.config?.schema?.styling || defaultStyling;
798
+ const styling = { ...defaultStyling, ...form.config?.schema?.styling, ...form.config?.styling };
799
799
  const radius = getBorderRadius(styling.borderRadius);
800
800
  const btnRadius = getButtonRadius(styling.buttonRadius);
801
801
  const fontSize = getFontSize(styling.fontSize);
@@ -1078,14 +1078,26 @@ function FormFieldInput({
1078
1078
  return field.options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
1079
1079
  };
1080
1080
  if (field.type === "heading") {
1081
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: { marginBottom: fieldSpacing, fontSize: getHeadingSize(styling.headingSize), fontWeight: 600 }, children: field.content || field.label });
1081
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: fieldSpacing }, children: [
1082
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: { fontSize: getHeadingSize(styling.headingSize), fontWeight: 600 }, children: field.label }),
1083
+ field.content && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontSize: "0.875rem", color: styling.theme === "dark" ? "#9ca3af" : "#6b7280", marginTop: "0.25rem" }, children: field.content })
1084
+ ] });
1082
1085
  }
1083
1086
  if (field.type === "divider") {
1084
1087
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("hr", { style: { marginBottom: fieldSpacing, border: "none", borderTop: `1px solid ${styling.theme === "dark" ? "#4b5563" : "#d1d5db"}` } });
1085
1088
  }
1086
1089
  if (field.type === "paragraph") {
1087
1090
  const pSize = field.paragraphFontSize ? `${field.paragraphFontSize}px` : getParagraphSize(styling.paragraphSize);
1088
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { marginBottom: fieldSpacing, fontSize: pSize, color: styling.theme === "dark" ? "#9ca3af" : "#6b7280" }, children: field.content || field.label });
1091
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: fieldSpacing }, children: [
1092
+ field.label && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontWeight: 500, fontSize: pSize, marginBottom: "0.25rem" }, children: field.label }),
1093
+ field.content && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1094
+ "div",
1095
+ {
1096
+ style: { fontSize: pSize, color: styling.theme === "dark" ? "#9ca3af" : "#6b7280" },
1097
+ dangerouslySetInnerHTML: { __html: field.content }
1098
+ }
1099
+ )
1100
+ ] });
1089
1101
  }
1090
1102
  if (field.type === "hidden") {
1091
1103
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "hidden", name: field.name, value: String(value || field.defaultValue || "") });