@forms.expert/sdk 0.2.10 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +3 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js.map +1 -1
- package/dist/react/index.cjs +49 -13
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +3 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +49 -13
- package/dist/react/index.js.map +1 -1
- package/dist/vanilla/index.cjs +19 -19
- package/dist/vanilla/index.cjs.map +1 -1
- package/dist/vanilla/index.d.cts +4 -0
- package/dist/vanilla/index.d.ts +4 -0
- package/dist/vanilla/index.global.js +19 -19
- package/dist/vanilla/index.global.js.map +1 -1
- package/dist/vanilla/index.js +24 -24
- package/dist/vanilla/index.js.map +1 -1
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.d.cts +3 -0
- package/dist/vue/index.d.ts +3 -0
- package/dist/vue/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/index.cjs.map
CHANGED
|
@@ -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 * Secondary button configuration\n */\nexport interface SecondaryButton {\n enabled: boolean;\n text: string;\n href: string;\n openInNewTab?: boolean;\n position?: 'left' | 'right' | 'below';\n align?: 'left' | 'center' | 'right';\n marginTop?: number;\n marginBottom?: number;\n style?: 'filled' | 'outlined' | 'minimal' | 'link';\n color?: string;\n textColor?: string;\n fontSize?: number;\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 fieldBorderStyle?: 'full' | 'bottom';\n buttonColor?: string;\n buttonText?: string;\n buttonRadius?: 'none' | 'small' | 'medium' | 'large' | 'full';\n buttonAlign?: 'left' | 'center' | 'right';\n buttonSize?: 'small' | 'medium' | 'large';\n buttonPaddingX?: number;\n buttonPaddingY?: number;\n buttonGradient?: string;\n buttonFullWidth?: boolean;\n buttonFontSize?: number;\n fieldSpacing?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n formPadding?: 'none' | '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 formNameFontSize?: number;\n formNameFontWeight?: 'normal' | 'medium' | 'semibold' | 'bold';\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 secondaryButton?: SecondaryButton;\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 showFormName?: boolean;\n };\n /** Access control configuration */\n accessControl?: {\n mode: string;\n passwordProtected: boolean;\n };\n /** Branding configuration */\n branding?: FormBranding;\n /** Hosted config (page settings, success page, etc.) */\n hostedConfig?: {\n pageTitle?: string;\n successMessage?: string;\n successPageType?: 'message' | 'redirect' | 'custom';\n redirectUrl?: string;\n customSuccessHtml?: string;\n [key: string]: unknown;\n };\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' }, body: '{}' }).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/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 * Secondary button configuration\n */\nexport interface SecondaryButton {\n enabled: boolean;\n text: string;\n href: string;\n openInNewTab?: boolean;\n position?: 'left' | 'right' | 'below';\n align?: 'left' | 'center' | 'right';\n marginTop?: number;\n marginBottom?: number;\n style?: 'filled' | 'outlined' | 'minimal' | 'link';\n color?: string;\n textColor?: string;\n fontSize?: number;\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 labelClassName?: string;\n fieldClassName?: string;\n buttonClassName?: string;\n customCss?: string;\n // Extended styling (matching hosted form builder)\n fontFamily?: string;\n formWidth?: 'narrow' | 'medium' | 'wide' | 'full';\n fieldLayout?: 'stacked' | 'inline';\n fieldBorderStyle?: 'full' | 'bottom';\n buttonColor?: string;\n buttonText?: string;\n buttonRadius?: 'none' | 'small' | 'medium' | 'large' | 'full';\n buttonAlign?: 'left' | 'center' | 'right';\n buttonSize?: 'small' | 'medium' | 'large';\n buttonPaddingX?: number;\n buttonPaddingY?: number;\n buttonGradient?: string;\n buttonFullWidth?: boolean;\n buttonFontSize?: number;\n fieldSpacing?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n formPadding?: 'none' | '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 formNameFontSize?: number;\n formNameFontWeight?: 'normal' | 'medium' | 'semibold' | 'bold';\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 secondaryButton?: SecondaryButton;\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 showFormName?: boolean;\n };\n /** Access control configuration */\n accessControl?: {\n mode: string;\n passwordProtected: boolean;\n };\n /** Branding configuration */\n branding?: FormBranding;\n /** Hosted config (page settings, success page, etc.) */\n hostedConfig?: {\n pageTitle?: string;\n successMessage?: string;\n successPageType?: 'message' | 'redirect' | 'custom';\n redirectUrl?: string;\n customSuccessHtml?: string;\n [key: string]: unknown;\n };\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' }, body: '{}' }).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":[]}
|
package/dist/core/index.d.cts
CHANGED
|
@@ -78,6 +78,9 @@ interface FormStyling {
|
|
|
78
78
|
fontSize: 'sm' | 'md' | 'lg';
|
|
79
79
|
buttonStyle: 'filled' | 'outline';
|
|
80
80
|
labelPosition: 'top' | 'left' | 'floating';
|
|
81
|
+
labelClassName?: string;
|
|
82
|
+
fieldClassName?: string;
|
|
83
|
+
buttonClassName?: string;
|
|
81
84
|
customCss?: string;
|
|
82
85
|
fontFamily?: string;
|
|
83
86
|
formWidth?: 'narrow' | 'medium' | 'wide' | 'full';
|
package/dist/core/index.d.ts
CHANGED
|
@@ -78,6 +78,9 @@ interface FormStyling {
|
|
|
78
78
|
fontSize: 'sm' | 'md' | 'lg';
|
|
79
79
|
buttonStyle: 'filled' | 'outline';
|
|
80
80
|
labelPosition: 'top' | 'left' | 'floating';
|
|
81
|
+
labelClassName?: string;
|
|
82
|
+
fieldClassName?: string;
|
|
83
|
+
buttonClassName?: string;
|
|
81
84
|
customCss?: string;
|
|
82
85
|
fontFamily?: string;
|
|
83
86
|
formWidth?: 'narrow' | 'medium' | 'wide' | 'full';
|
package/dist/core/index.js.map
CHANGED
|
@@ -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 * Secondary button configuration\n */\nexport interface SecondaryButton {\n enabled: boolean;\n text: string;\n href: string;\n openInNewTab?: boolean;\n position?: 'left' | 'right' | 'below';\n align?: 'left' | 'center' | 'right';\n marginTop?: number;\n marginBottom?: number;\n style?: 'filled' | 'outlined' | 'minimal' | 'link';\n color?: string;\n textColor?: string;\n fontSize?: number;\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 fieldBorderStyle?: 'full' | 'bottom';\n buttonColor?: string;\n buttonText?: string;\n buttonRadius?: 'none' | 'small' | 'medium' | 'large' | 'full';\n buttonAlign?: 'left' | 'center' | 'right';\n buttonSize?: 'small' | 'medium' | 'large';\n buttonPaddingX?: number;\n buttonPaddingY?: number;\n buttonGradient?: string;\n buttonFullWidth?: boolean;\n buttonFontSize?: number;\n fieldSpacing?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n formPadding?: 'none' | '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 formNameFontSize?: number;\n formNameFontWeight?: 'normal' | 'medium' | 'semibold' | 'bold';\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 secondaryButton?: SecondaryButton;\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 showFormName?: boolean;\n };\n /** Access control configuration */\n accessControl?: {\n mode: string;\n passwordProtected: boolean;\n };\n /** Branding configuration */\n branding?: FormBranding;\n /** Hosted config (page settings, success page, etc.) */\n hostedConfig?: {\n pageTitle?: string;\n successMessage?: string;\n successPageType?: 'message' | 'redirect' | 'custom';\n redirectUrl?: string;\n customSuccessHtml?: string;\n [key: string]: unknown;\n };\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' }, body: '{}' }).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 * Secondary button configuration\n */\nexport interface SecondaryButton {\n enabled: boolean;\n text: string;\n href: string;\n openInNewTab?: boolean;\n position?: 'left' | 'right' | 'below';\n align?: 'left' | 'center' | 'right';\n marginTop?: number;\n marginBottom?: number;\n style?: 'filled' | 'outlined' | 'minimal' | 'link';\n color?: string;\n textColor?: string;\n fontSize?: number;\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 labelClassName?: string;\n fieldClassName?: string;\n buttonClassName?: string;\n customCss?: string;\n // Extended styling (matching hosted form builder)\n fontFamily?: string;\n formWidth?: 'narrow' | 'medium' | 'wide' | 'full';\n fieldLayout?: 'stacked' | 'inline';\n fieldBorderStyle?: 'full' | 'bottom';\n buttonColor?: string;\n buttonText?: string;\n buttonRadius?: 'none' | 'small' | 'medium' | 'large' | 'full';\n buttonAlign?: 'left' | 'center' | 'right';\n buttonSize?: 'small' | 'medium' | 'large';\n buttonPaddingX?: number;\n buttonPaddingY?: number;\n buttonGradient?: string;\n buttonFullWidth?: boolean;\n buttonFontSize?: number;\n fieldSpacing?: 'compact' | 'normal' | 'relaxed' | 'spacious';\n formPadding?: 'none' | '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 formNameFontSize?: number;\n formNameFontWeight?: 'normal' | 'medium' | 'semibold' | 'bold';\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 secondaryButton?: SecondaryButton;\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 showFormName?: boolean;\n };\n /** Access control configuration */\n accessControl?: {\n mode: string;\n passwordProtected: boolean;\n };\n /** Branding configuration */\n branding?: FormBranding;\n /** Hosted config (page settings, success page, etc.) */\n hostedConfig?: {\n pageTitle?: string;\n successMessage?: string;\n successPageType?: 'message' | 'redirect' | 'custom';\n redirectUrl?: string;\n customSuccessHtml?: string;\n [key: string]: unknown;\n };\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' }, body: '{}' }).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":[]}
|
package/dist/react/index.cjs
CHANGED
|
@@ -815,7 +815,34 @@ function FormsExpertForm({
|
|
|
815
815
|
const resolvedButtonText = styling.buttonText || submitText;
|
|
816
816
|
const googleFonts = ["Inter", "Roboto", "Open Sans", "Lato", "Poppins", "Montserrat", "Nunito", "Source Sans Pro", "Raleway", "Ubuntu", "Playfair Display", "Merriweather"];
|
|
817
817
|
const fontToLoad = styling.fontFamily?.split(",")[0]?.trim();
|
|
818
|
-
const
|
|
818
|
+
const hasExistingFonts = (0, import_react2.useMemo)(() => {
|
|
819
|
+
if (typeof document === "undefined") return false;
|
|
820
|
+
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
821
|
+
for (const link of links) {
|
|
822
|
+
const href = link.href || "";
|
|
823
|
+
if (href.includes("fonts.googleapis.com") || href.includes("fonts.gstatic.com") || href.includes("typekit") || href.includes("fonts.")) {
|
|
824
|
+
return true;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
for (const sheet of document.styleSheets) {
|
|
829
|
+
try {
|
|
830
|
+
const rules = sheet.cssRules || sheet.rules;
|
|
831
|
+
if (rules) {
|
|
832
|
+
for (const rule of rules) {
|
|
833
|
+
if (rule instanceof CSSFontFaceRule) {
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
} catch {
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
} catch {
|
|
842
|
+
}
|
|
843
|
+
return false;
|
|
844
|
+
}, []);
|
|
845
|
+
const googleFontUrl = fontToLoad && googleFonts.includes(fontToLoad) && !hasExistingFonts ? `https://fonts.googleapis.com/css2?family=${fontToLoad.replace(/ /g, "+")}:wght@400;500;600;700&display=swap` : null;
|
|
819
846
|
const handleSubmit = async (e) => {
|
|
820
847
|
e.preventDefault();
|
|
821
848
|
const result = await form.submit(captchaToken || void 0);
|
|
@@ -1018,7 +1045,7 @@ function FormsExpertForm({
|
|
|
1018
1045
|
}
|
|
1019
1046
|
}
|
|
1020
1047
|
),
|
|
1021
|
-
form.config.settings?.showFormName !== false && form.config.name && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("
|
|
1048
|
+
form.config.settings?.showFormName !== false && form.config.name && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
|
|
1022
1049
|
fontSize: styling.formNameFontSize != null ? `${styling.formNameFontSize}px` : "1.5rem",
|
|
1023
1050
|
fontWeight: styling.formNameFontWeight === "normal" ? 400 : styling.formNameFontWeight === "medium" ? 500 : styling.formNameFontWeight === "semibold" ? 600 : 700,
|
|
1024
1051
|
marginBottom: "0.5rem",
|
|
@@ -1086,6 +1113,7 @@ function FormsExpertForm({
|
|
|
1086
1113
|
{
|
|
1087
1114
|
type: "submit",
|
|
1088
1115
|
disabled: form.isLoading,
|
|
1116
|
+
className: styling.buttonClassName,
|
|
1089
1117
|
style: {
|
|
1090
1118
|
...styling.buttonFullWidth ? { width: "100%" } : styling.buttonAlign ? {} : { width: "100%" },
|
|
1091
1119
|
padding: `${btnPy} ${btnPx}`,
|
|
@@ -1229,7 +1257,7 @@ function FormFieldInput({
|
|
|
1229
1257
|
style: { width: "1rem", height: "1rem", accentColor: styling.primaryColor }
|
|
1230
1258
|
}
|
|
1231
1259
|
),
|
|
1232
|
-
(field.label || field.type === "consent" && field.consentText) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { htmlFor: field.name, children: [
|
|
1260
|
+
(field.label || field.type === "consent" && field.consentText) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { htmlFor: field.name, className: styling.labelClassName, children: [
|
|
1233
1261
|
field.type === "consent" && field.consentText ? field.consentUrl ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
|
|
1234
1262
|
field.consentText,
|
|
1235
1263
|
" ",
|
|
@@ -1244,6 +1272,7 @@ function FormFieldInput({
|
|
|
1244
1272
|
"label",
|
|
1245
1273
|
{
|
|
1246
1274
|
htmlFor: field.name,
|
|
1275
|
+
className: styling.labelClassName,
|
|
1247
1276
|
style: {
|
|
1248
1277
|
display: "block",
|
|
1249
1278
|
fontWeight: 500,
|
|
@@ -1267,11 +1296,12 @@ function FormFieldInput({
|
|
|
1267
1296
|
onChange,
|
|
1268
1297
|
placeholder: field.placeholder,
|
|
1269
1298
|
required: field.required,
|
|
1270
|
-
style: { ...inputStyle, minHeight: "100px", resize: "vertical" }
|
|
1299
|
+
style: { ...inputStyle, minHeight: "100px", resize: "vertical" },
|
|
1300
|
+
className: styling.fieldClassName
|
|
1271
1301
|
}
|
|
1272
1302
|
);
|
|
1273
1303
|
} else if (field.type === "select" || field.type === "dropdown") {
|
|
1274
|
-
fieldEl = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { id: field.name, name: field.name, value: String(value || ""), onChange, required: field.required, style: inputStyle, children: [
|
|
1304
|
+
fieldEl = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { id: field.name, name: field.name, value: String(value || ""), onChange, required: field.required, style: inputStyle, className: styling.fieldClassName, children: [
|
|
1275
1305
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: "Select an option..." }),
|
|
1276
1306
|
getOpts().map((opt) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
|
|
1277
1307
|
] });
|
|
@@ -1415,7 +1445,8 @@ function FormFieldInput({
|
|
|
1415
1445
|
required: field.required,
|
|
1416
1446
|
accept: field.allowedMimeTypes?.join(","),
|
|
1417
1447
|
multiple: field.multiple,
|
|
1418
|
-
style: inputStyle
|
|
1448
|
+
style: inputStyle,
|
|
1449
|
+
className: styling.fieldClassName
|
|
1419
1450
|
}
|
|
1420
1451
|
);
|
|
1421
1452
|
} else if (field.type === "name") {
|
|
@@ -1428,7 +1459,8 @@ function FormFieldInput({
|
|
|
1428
1459
|
placeholder: labels[nf] || nf,
|
|
1429
1460
|
value: nameVal[nf] || "",
|
|
1430
1461
|
onChange: (e) => onValueChange(field.name, { ...nameVal, [nf]: e.target.value }),
|
|
1431
|
-
style: { ...inputStyle, flex: 1, minWidth: "120px" }
|
|
1462
|
+
style: { ...inputStyle, flex: 1, minWidth: "120px" },
|
|
1463
|
+
className: styling.fieldClassName
|
|
1432
1464
|
},
|
|
1433
1465
|
nf
|
|
1434
1466
|
)) });
|
|
@@ -1442,7 +1474,8 @@ function FormFieldInput({
|
|
|
1442
1474
|
placeholder: labels[af] || af,
|
|
1443
1475
|
value: addr[af] || "",
|
|
1444
1476
|
onChange: (e) => onValueChange(field.name, { ...addr, [af]: e.target.value }),
|
|
1445
|
-
style: inputStyle
|
|
1477
|
+
style: inputStyle,
|
|
1478
|
+
className: styling.fieldClassName
|
|
1446
1479
|
},
|
|
1447
1480
|
af
|
|
1448
1481
|
)) });
|
|
@@ -1461,7 +1494,8 @@ function FormFieldInput({
|
|
|
1461
1494
|
min: field.min,
|
|
1462
1495
|
max: field.max,
|
|
1463
1496
|
required: field.required,
|
|
1464
|
-
style: inputStyle
|
|
1497
|
+
style: inputStyle,
|
|
1498
|
+
className: styling.fieldClassName
|
|
1465
1499
|
}
|
|
1466
1500
|
)
|
|
1467
1501
|
] });
|
|
@@ -1484,16 +1518,17 @@ function FormFieldInput({
|
|
|
1484
1518
|
value: String(value || ""),
|
|
1485
1519
|
onChange: (e) => onValueChange(field.name, e.target.value),
|
|
1486
1520
|
placeholder: "#000000",
|
|
1487
|
-
style: { ...inputStyle, flex: 1 }
|
|
1521
|
+
style: { ...inputStyle, flex: 1 },
|
|
1522
|
+
className: styling.fieldClassName
|
|
1488
1523
|
}
|
|
1489
1524
|
)
|
|
1490
1525
|
] });
|
|
1491
1526
|
} else if (field.type === "dateRange") {
|
|
1492
1527
|
const range = value || {};
|
|
1493
1528
|
fieldEl = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: "0.5rem", alignItems: "center" }, children: [
|
|
1494
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "date", value: range.start || "", onChange: (e) => onValueChange(field.name, { ...range, start: e.target.value }), style: { ...inputStyle, flex: 1 } }),
|
|
1529
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "date", value: range.start || "", onChange: (e) => onValueChange(field.name, { ...range, start: e.target.value }), style: { ...inputStyle, flex: 1 }, className: styling.fieldClassName }),
|
|
1495
1530
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "to" }),
|
|
1496
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "date", value: range.end || "", onChange: (e) => onValueChange(field.name, { ...range, end: e.target.value }), style: { ...inputStyle, flex: 1 } })
|
|
1531
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "date", value: range.end || "", onChange: (e) => onValueChange(field.name, { ...range, end: e.target.value }), style: { ...inputStyle, flex: 1 }, className: styling.fieldClassName })
|
|
1497
1532
|
] });
|
|
1498
1533
|
} else {
|
|
1499
1534
|
const typeMap = {
|
|
@@ -1520,7 +1555,8 @@ function FormFieldInput({
|
|
|
1520
1555
|
max: field.max,
|
|
1521
1556
|
step: field.step,
|
|
1522
1557
|
maxLength: field.maxLength,
|
|
1523
|
-
style: inputStyle
|
|
1558
|
+
style: inputStyle,
|
|
1559
|
+
className: styling.fieldClassName
|
|
1524
1560
|
}
|
|
1525
1561
|
);
|
|
1526
1562
|
}
|