@page-speed/forms 0.4.2 → 0.4.4

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.
@@ -164,7 +164,11 @@ var BlockErrorBoundary = class extends React__namespace.Component {
164
164
  return { error };
165
165
  }
166
166
  componentDidCatch(error, errorInfo) {
167
- console.error(`Block render error (${this.props.block._id}):`, error, errorInfo);
167
+ console.error(
168
+ `Block render error (${this.props.block._id}):`,
169
+ error,
170
+ errorInfo
171
+ );
168
172
  }
169
173
  render() {
170
174
  if (this.state.error) {
@@ -174,7 +178,7 @@ var BlockErrorBoundary = class extends React__namespace.Component {
174
178
  return /* @__PURE__ */ React__namespace.createElement(
175
179
  "div",
176
180
  {
177
- className: "block-error border border-red-300 bg-red-50 p-4 rounded text-red-700",
181
+ className: "block-error border border-destructive bg-destructive p-4 rounded text-destructive-foreground",
178
182
  "data-block-id": this.props.block._id,
179
183
  "data-block-type": this.props.block._type
180
184
  },
@@ -193,7 +197,11 @@ function createBlockAdapter(Component2, options = {}) {
193
197
  withErrorBoundary = true,
194
198
  errorFallback
195
199
  } = options;
196
- const AdaptedComponent = ({ block, children, renderChildren }) => {
200
+ const AdaptedComponent = ({
201
+ block,
202
+ children,
203
+ renderChildren
204
+ }) => {
197
205
  const blockProps = block.blockProps || {};
198
206
  const mergedProps = { ...defaultProps, ...blockProps };
199
207
  const finalProps = transformProps ? transformProps(mergedProps, block) : mergedProps;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/integration/ContactFormSerializer.ts","../src/integration/BlockAdapter.tsx"],"names":["camelField","errorMessage","React","Component"],"mappings":"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","file":"integration.cjs","sourcesContent":["/**\n * @page-speed/forms - Rails API Serializer\n *\n * Serializes form data for the DashTrack ContactsController API.\n * Handles field name conversion (camelCase → snake_case), custom fields separation,\n * and upload token arrays.\n *\n * @see https://github.com/opensite-ai/page-speed-forms\n */\n\n/**\n * Standard fields recognized by the Rails ContactsController API.\n * These are serialized to snake_case and sent in the contact object.\n */\nconst STANDARD_FIELDS = [\n \"content\",\n \"email\",\n \"firstName\",\n \"lastName\",\n \"locationId\",\n \"phone\",\n \"subject\",\n \"redemptionStatus\",\n \"birthday\",\n \"city\",\n \"state\",\n \"websiteFormAssignmentId\",\n \"websiteId\",\n \"acceptsSmsMarketing\",\n \"acceptsEmailMarketing\",\n \"visitorIpAddress\",\n] as const;\n\n/**\n * Configuration parameters for Rails API submission.\n */\nexport interface RailsApiConfig {\n /**\n * API key for authentication.\n * Sent as top-level parameter: api_key\n */\n apiKey: string;\n\n /**\n * Contact category token for categorization.\n * Sent as top-level parameter: contact_category_token\n */\n contactCategoryToken?: string;\n\n /**\n * Location ID for multi-location organizations.\n * Sent as top-level parameter: location_id\n */\n locationId?: string;\n\n /**\n * Website ID for tracking form submissions.\n * Sent within contact object: website_id\n */\n websiteId?: string;\n\n /**\n * Website form assignment ID for form tracking.\n * Sent within contact object: website_form_assignment_id\n */\n websiteFormAssignmentId?: string;\n\n /**\n * Visitor IP address. If not provided, will be auto-detected on server.\n * Sent within contact object: visitor_ip_address\n */\n visitorIpAddress?: string;\n}\n\n/**\n * Serialized form data ready for Rails API submission.\n */\nexport interface SerializedFormData {\n /**\n * Top-level API key parameter.\n */\n api_key: string;\n\n /**\n * Top-level contact category token (optional).\n */\n contact_category_token?: string;\n\n /**\n * Top-level location ID (optional).\n */\n location_id?: string;\n\n /**\n * Contact object with standard fields and metadata.\n */\n contact: {\n /**\n * Standard contact fields in snake_case.\n */\n [key: string]: unknown;\n\n /**\n * Custom fields that don't match standard schema.\n * Stored as separate hash in Rails.\n */\n custom_fields?: Record<string, unknown>;\n\n /**\n * Array of upload tokens from file uploads.\n * These reference ContactFormUpload records in Rails.\n */\n contact_form_upload_tokens?: string[];\n };\n}\n\n/**\n * Form values from the form library (camelCase keys).\n */\nexport type FormValues = Record<string, unknown>;\n\n/**\n * Convert camelCase to snake_case.\n *\n * @example\n * ```ts\n * camelToSnake(\"firstName\") // \"first_name\"\n * camelToSnake(\"acceptsSmsMarketing\") // \"accepts_sms_marketing\"\n * ```\n */\nfunction camelToSnake(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/**\n * Check if a field name is a standard Rails contact field.\n */\nfunction isStandardField(fieldName: string): boolean {\n return STANDARD_FIELDS.includes(fieldName as (typeof STANDARD_FIELDS)[number]);\n}\n\n/**\n * Extract upload tokens from form values.\n * Handles both string tokens and arrays of tokens.\n */\nfunction extractUploadTokens(values: FormValues): string[] {\n const tokens: string[] = [];\n\n for (const value of Object.values(values)) {\n if (typeof value === \"string\" && value.startsWith(\"upload_\")) {\n tokens.push(value);\n } else if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item === \"string\" && item.startsWith(\"upload_\")) {\n tokens.push(item);\n }\n }\n }\n }\n\n return tokens;\n}\n\n/**\n * Format date/time values for Rails API.\n * Rails expects ISO 8601 format.\n */\nfunction formatDateForRails(value: unknown): string | undefined {\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (typeof value === \"string\") {\n // Attempt to parse and reformat to ensure valid ISO 8601\n const date = new Date(value);\n if (!isNaN(date.getTime())) {\n return date.toISOString();\n }\n }\n return undefined;\n}\n\n/**\n * Serialize form values for Rails ContactsController API.\n *\n * This function:\n * 1. Converts camelCase field names to snake_case\n * 2. Separates standard fields from custom fields\n * 3. Extracts upload tokens into contact_form_upload_tokens array\n * 4. Formats dates to ISO 8601\n * 5. Includes API configuration parameters\n *\n * @param values - Form values from useForm hook (camelCase keys)\n * @param config - Rails API configuration (apiKey, locationId, etc.)\n * @returns Serialized data ready for POST to /contacts\n *\n * @example\n * ```ts\n * const serialized = serializeForRails(\n * {\n * firstName: \"John\",\n * lastName: \"Doe\",\n * email: \"john@example.com\",\n * phone: \"555-1234\",\n * companySize: \"50-100\", // Custom field\n * resumeToken: \"upload_abc123\",\n * },\n * {\n * apiKey: \"key_123\",\n * contactCategoryToken: \"cat_xyz\",\n * locationId: \"loc_456\",\n * }\n * );\n *\n * // Result:\n * // {\n * // api_key: \"key_123\",\n * // contact_category_token: \"cat_xyz\",\n * // location_id: \"loc_456\",\n * // contact: {\n * // first_name: \"John\",\n * // last_name: \"Doe\",\n * // email: \"john@example.com\",\n * // phone: \"555-1234\",\n * // custom_fields: {\n * // company_size: \"50-100\"\n * // },\n * // contact_form_upload_tokens: [\"upload_abc123\"]\n * // }\n * // }\n * ```\n */\nexport function serializeForRails(\n values: FormValues,\n config: RailsApiConfig\n): SerializedFormData {\n const standardFields: Record<string, unknown> = {};\n const customFields: Record<string, unknown> = {};\n\n // Extract upload tokens\n const uploadTokens = extractUploadTokens(values);\n\n // Separate standard and custom fields\n for (const [key, value] of Object.entries(values)) {\n // Skip upload token fields - they're handled separately\n if (typeof value === \"string\" && value.startsWith(\"upload_\")) {\n continue;\n }\n if (Array.isArray(value) && value.every(\n (item) => typeof item === \"string\" && item.startsWith(\"upload_\")\n )) {\n continue;\n }\n\n const snakeKey = camelToSnake(key);\n\n if (isStandardField(key)) {\n // Format dates for birthday field\n if (key === \"birthday\") {\n const formatted = formatDateForRails(value);\n if (formatted) {\n standardFields[snakeKey] = formatted;\n }\n } else {\n standardFields[snakeKey] = value;\n }\n } else {\n // Custom fields\n customFields[snakeKey] = value;\n }\n }\n\n // Add config fields to standard fields if provided\n if (config.websiteId !== undefined) {\n standardFields.website_id = config.websiteId;\n }\n if (config.websiteFormAssignmentId !== undefined) {\n standardFields.website_form_assignment_id = config.websiteFormAssignmentId;\n }\n if (config.visitorIpAddress !== undefined) {\n standardFields.visitor_ip_address = config.visitorIpAddress;\n }\n\n // Build contact object\n const contact: SerializedFormData[\"contact\"] = {\n ...standardFields,\n };\n\n // Add custom fields if any\n if (Object.keys(customFields).length > 0) {\n contact.custom_fields = customFields;\n }\n\n // Add upload tokens if any\n if (uploadTokens.length > 0) {\n contact.contact_form_upload_tokens = uploadTokens;\n }\n\n // Build final serialized data\n const serialized: SerializedFormData = {\n api_key: config.apiKey,\n contact,\n };\n\n // Add optional top-level parameters\n if (config.contactCategoryToken !== undefined) {\n serialized.contact_category_token = config.contactCategoryToken;\n }\n if (config.locationId !== undefined) {\n serialized.location_id = config.locationId;\n }\n\n return serialized;\n}\n\n/**\n * Rails API error response format.\n */\nexport interface RailsErrorResponse {\n errors: {\n [field: string]: string[];\n };\n status: number;\n}\n\n/**\n * Form error format used by @page-speed/forms.\n */\nexport type FormErrors = Record<string, string | undefined>;\n\n/**\n * Convert snake_case to camelCase.\n *\n * @example\n * ```ts\n * snakeToCamel(\"first_name\") // \"firstName\"\n * snakeToCamel(\"accepts_sms_marketing\") // \"acceptsSmsMarketing\"\n * ```\n */\nfunction snakeToCamel(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/**\n * Deserialize Rails API errors to form error format.\n *\n * Converts Rails error format to the format expected by @page-speed/forms:\n * - Maps snake_case field names to camelCase\n * - Flattens error arrays to single error string (first error)\n * - Handles custom_fields errors by mapping back to original field names\n * - Extracts base errors to form-level errors\n *\n * @param railsErrors - Error response from Rails API\n * @returns Form errors object (camelCase keys)\n *\n * @example\n * ```ts\n * const formErrors = deserializeErrors({\n * errors: {\n * first_name: [\"can't be blank\", \"is too short\"],\n * email: [\"is invalid\"],\n * custom_fields: {\n * company_size: [\"is required\"]\n * },\n * base: [\"Something went wrong\"]\n * },\n * status: 422\n * });\n *\n * // Result:\n * // {\n * // firstName: \"can't be blank\",\n * // email: \"is invalid\",\n * // companySize: \"is required\",\n * // _form: \"Something went wrong\"\n * // }\n * ```\n */\nexport function deserializeErrors(railsErrors: RailsErrorResponse): FormErrors {\n const formErrors: FormErrors = {};\n\n for (const [field, messages] of Object.entries(railsErrors.errors)) {\n // Handle base errors (form-level errors)\n if (field === \"base\") {\n formErrors._form = Array.isArray(messages) ? messages[0] : messages;\n continue;\n }\n\n // Handle custom_fields errors\n if (field === \"custom_fields\" && typeof messages === \"object\") {\n for (const [customField, customMessages] of Object.entries(messages)) {\n const camelField = snakeToCamel(customField);\n const errorMessage = Array.isArray(customMessages)\n ? customMessages[0]\n : customMessages;\n formErrors[camelField] = errorMessage;\n }\n continue;\n }\n\n // Handle standard field errors\n const camelField = snakeToCamel(field);\n const errorMessage = Array.isArray(messages) ? messages[0] : messages;\n formErrors[camelField] = errorMessage;\n }\n\n return formErrors;\n}\n","/**\n * @page-speed/forms - Block Adapter\n *\n * Adapts form components for use in block-based rendering systems.\n * Wraps form components to accept block prop structure and transforms them\n * to component-native props.\n *\n * @see https://github.com/opensite-ai/page-speed-forms\n */\n\n\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Block structure for design payloads.\n * Minimal type definition for adapter compatibility.\n */\nexport interface Block {\n _id: string;\n _type: string;\n _name?: string;\n _parent?: string | null;\n tag?: string;\n styles?: string;\n content?: string;\n blockProps?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Options for Block adapter.\n */\nexport interface BlockAdapterOptions {\n /**\n * Default props to merge with block props.\n */\n defaultProps?: Record<string, unknown>;\n\n /**\n * Transform function to convert Block props to component props.\n * Useful for custom prop mapping logic.\n */\n transformProps?: (blockProps: Record<string, unknown>, block: Block) => Record<string, unknown>;\n\n /**\n * Extract display name from block for debugging.\n * Defaults to using _name or _type from block.\n */\n getDisplayName?: (block: Block) => string;\n\n /**\n * Enable React error boundary wrapping.\n * Defaults to true.\n */\n withErrorBoundary?: boolean;\n\n /**\n * Custom error fallback component.\n * If not provided, renders basic error message.\n */\n errorFallback?: (error: Error, block: Block) => React.ReactNode;\n}\n\n/**\n * Props passed to adapted component.\n */\nexport interface AdaptedComponentProps {\n /**\n * Block data from design payload.\n */\n block: Block;\n\n /**\n * Child blocks for rendering nested content.\n * Used by container components like Form.\n */\n children?: React.ReactNode;\n\n /**\n * Callback to render child blocks.\n * Provided by block rendering systems.\n */\n renderChildren?: (blockId: string) => React.ReactNode;\n}\n\n/**\n * Error boundary component for catching render errors.\n */\nclass BlockErrorBoundary extends React.Component<\n {\n block: Block;\n fallback?: (error: Error, block: Block) => React.ReactNode;\n children: React.ReactNode;\n },\n { error: Error | null }\n> {\n constructor(props: BlockErrorBoundary[\"props\"]) {\n super(props);\n this.state = { error: null };\n }\n\n static getDerivedStateFromError(error: Error) {\n return { error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n console.error(`Block render error (${this.props.block._id}):`, error, errorInfo);\n }\n\n render() {\n if (this.state.error) {\n if (this.props.fallback) {\n return this.props.fallback(this.state.error, this.props.block);\n }\n\n return (\n <div\n className=\"block-error border border-red-300 bg-red-50 p-4 rounded text-red-700\"\n data-block-id={this.props.block._id}\n data-block-type={this.props.block._type}\n >\n <p className=\"font-semibold\">Block Render Error</p>\n <p className=\"text-sm\">\n Block: {this.props.block._name || this.props.block._id} ({this.props.block._type})\n </p>\n <p className=\"text-sm mt-1\">{this.state.error.message}</p>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n\n/**\n * Create a Block-compatible wrapper for a form component.\n *\n * This adapter transforms Block props (from design payload) into\n * component-native props. It handles:\n * - Extracting props from `blockProps` field\n * - Merging with default props\n * - Custom prop transformation\n * - Error boundary wrapping\n * - Children rendering support\n *\n * @param Component - React component to adapt\n * @param options - Adapter configuration options\n * @returns Block-compatible component\n *\n * @example\n * ```tsx\n * import { TextInput } from \"@page-speed/forms/inputs\";\n * import { createBlockAdapter } from \"@page-speed/forms/integration\";\n *\n * // Create Block-compatible TextInput\n * const BlockTextInput = createBlockAdapter(TextInput, {\n * defaultProps: {\n * placeholder: \"Enter text...\",\n * },\n * transformProps: (blockProps, block) => ({\n * ...blockProps,\n * // Map Block content to component props\n * label: block.content || blockProps.label,\n * // Apply Block styles as className\n * className: block.styles,\n * }),\n * });\n *\n * // Register with rendering system\n * registerBlockType(\"TextInput\", BlockTextInput);\n * ```\n *\n * @example\n * ```tsx\n * // Block from design payload:\n * {\n * _id: \"field-email\",\n * _type: \"TextInput\",\n * _name: \"Email Field\",\n * content: \"Email Address\",\n * styles: \"w-full mb-4\",\n * blockProps: {\n * name: \"email\",\n * type: \"email\",\n * placeholder: \"you@example.com\",\n * required: true,\n * }\n * }\n *\n * // Transformed to TextInput props:\n * <TextInput\n * name=\"email\"\n * type=\"email\"\n * placeholder=\"you@example.com\"\n * required={true}\n * label=\"Email Address\"\n * className=\"w-full mb-4\"\n * />\n * ```\n */\nexport function createBlockAdapter<TProps extends Record<string, unknown>>(\n Component: React.ComponentType<TProps>,\n options: BlockAdapterOptions = {}\n): React.ComponentType<AdaptedComponentProps> {\n const {\n defaultProps = {},\n transformProps,\n withErrorBoundary = true,\n errorFallback,\n } = options;\n\n const AdaptedComponent: React.FC<AdaptedComponentProps> = ({ block, children, renderChildren }) => {\n // Extract component props from blockProps\n const blockProps = (block.blockProps || {}) as Record<string, unknown>;\n\n // Merge with default props\n const mergedProps = { ...defaultProps, ...blockProps };\n\n // Apply custom transformation if provided\n const finalProps = transformProps\n ? transformProps(mergedProps, block)\n : mergedProps;\n\n // Add data attributes for debugging\n const dataAttrs = {\n \"data-block-id\": block._id,\n \"data-block-type\": block._type,\n ...(block._name && { \"data-block-name\": block._name }),\n };\n\n // Merge data attributes with final props\n const componentProps = {\n ...finalProps,\n ...dataAttrs,\n } as unknown as TProps;\n\n // Render children if renderChildren callback provided\n const renderedChildren = renderChildren ? renderChildren(block._id) : children;\n\n const element = (\n <Component {...componentProps}>\n {renderedChildren}\n </Component>\n );\n\n // Wrap with error boundary if enabled\n if (withErrorBoundary) {\n return (\n <BlockErrorBoundary block={block} fallback={errorFallback}>\n {element}\n </BlockErrorBoundary>\n );\n }\n\n return element;\n };\n\n // Set display name for debugging\n const componentName = Component.displayName || Component.name || \"Component\";\n AdaptedComponent.displayName = `BlockAdapter(${componentName})`;\n\n return AdaptedComponent;\n}\n\n/**\n * Standard prop transformer for form input components.\n *\n * Applies common transformations:\n * - Maps Block `content` to `label`\n * - Maps Block `styles` to `className`\n * - Preserves all blockProps\n *\n * @param blockProps - Props from Block.blockProps\n * @param block - Full Block object\n * @returns Transformed props for component\n *\n * @example\n * ```tsx\n * const BlockTextInput = createBlockAdapter(TextInput, {\n * transformProps: standardInputTransformer,\n * });\n * ```\n */\nexport function standardInputTransformer(\n blockProps: Record<string, unknown>,\n block: Block\n): Record<string, unknown> {\n return {\n ...blockProps,\n // Use content as label if not already provided\n ...(block.content && !blockProps.label && { label: block.content }),\n // Apply Block styles as className\n ...(block.styles && { className: block.styles }),\n };\n}\n\n/**\n * Create multiple Block adapters with shared options.\n *\n * Convenience function for adapting multiple components at once\n * with the same configuration.\n *\n * @param components - Record of component name to component\n * @param options - Shared adapter options\n * @returns Record of adapted components\n *\n * @example\n * ```tsx\n * import * as Inputs from \"@page-speed/forms/inputs\";\n * import { createBlockAdapters, standardInputTransformer } from \"@page-speed/forms/integration\";\n *\n * const BlockInputs = createBlockAdapters(\n * {\n * TextInput: Inputs.TextInput,\n * TextArea: Inputs.TextArea,\n * Select: Inputs.Select,\n * },\n * {\n * transformProps: standardInputTransformer,\n * withErrorBoundary: true,\n * }\n * );\n *\n * // BlockInputs.TextInput, BlockInputs.TextArea, BlockInputs.Select\n * // are now Block-compatible\n * ```\n */\nexport function createBlockAdapters<\n TComponents extends Record<string, React.ComponentType<any>>\n>(\n components: TComponents,\n options: BlockAdapterOptions = {}\n): Record<keyof TComponents, React.ComponentType<AdaptedComponentProps>> {\n const adapted: Record<string, React.ComponentType<AdaptedComponentProps>> = {};\n\n for (const [name, component] of Object.entries(components)) {\n adapted[name] = createBlockAdapter(component, options);\n }\n\n return adapted as Record<keyof TComponents, React.ComponentType<AdaptedComponentProps>>;\n}\n"]}
1
+ {"version":3,"sources":["../src/integration/ContactFormSerializer.ts","../src/integration/BlockAdapter.tsx"],"names":["camelField","errorMessage","React","Component"],"mappings":"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","file":"integration.cjs","sourcesContent":["/**\n * @page-speed/forms - Rails API Serializer\n *\n * Serializes form data for the DashTrack ContactsController API.\n * Handles field name conversion (camelCase → snake_case), custom fields separation,\n * and upload token arrays.\n *\n * @see https://github.com/opensite-ai/page-speed-forms\n */\n\n/**\n * Standard fields recognized by the Rails ContactsController API.\n * These are serialized to snake_case and sent in the contact object.\n */\nconst STANDARD_FIELDS = [\n \"content\",\n \"email\",\n \"firstName\",\n \"lastName\",\n \"locationId\",\n \"phone\",\n \"subject\",\n \"redemptionStatus\",\n \"birthday\",\n \"city\",\n \"state\",\n \"websiteFormAssignmentId\",\n \"websiteId\",\n \"acceptsSmsMarketing\",\n \"acceptsEmailMarketing\",\n \"visitorIpAddress\",\n] as const;\n\n/**\n * Configuration parameters for Rails API submission.\n */\nexport interface RailsApiConfig {\n /**\n * API key for authentication.\n * Sent as top-level parameter: api_key\n */\n apiKey: string;\n\n /**\n * Contact category token for categorization.\n * Sent as top-level parameter: contact_category_token\n */\n contactCategoryToken?: string;\n\n /**\n * Location ID for multi-location organizations.\n * Sent as top-level parameter: location_id\n */\n locationId?: string;\n\n /**\n * Website ID for tracking form submissions.\n * Sent within contact object: website_id\n */\n websiteId?: string;\n\n /**\n * Website form assignment ID for form tracking.\n * Sent within contact object: website_form_assignment_id\n */\n websiteFormAssignmentId?: string;\n\n /**\n * Visitor IP address. If not provided, will be auto-detected on server.\n * Sent within contact object: visitor_ip_address\n */\n visitorIpAddress?: string;\n}\n\n/**\n * Serialized form data ready for Rails API submission.\n */\nexport interface SerializedFormData {\n /**\n * Top-level API key parameter.\n */\n api_key: string;\n\n /**\n * Top-level contact category token (optional).\n */\n contact_category_token?: string;\n\n /**\n * Top-level location ID (optional).\n */\n location_id?: string;\n\n /**\n * Contact object with standard fields and metadata.\n */\n contact: {\n /**\n * Standard contact fields in snake_case.\n */\n [key: string]: unknown;\n\n /**\n * Custom fields that don't match standard schema.\n * Stored as separate hash in Rails.\n */\n custom_fields?: Record<string, unknown>;\n\n /**\n * Array of upload tokens from file uploads.\n * These reference ContactFormUpload records in Rails.\n */\n contact_form_upload_tokens?: string[];\n };\n}\n\n/**\n * Form values from the form library (camelCase keys).\n */\nexport type FormValues = Record<string, unknown>;\n\n/**\n * Convert camelCase to snake_case.\n *\n * @example\n * ```ts\n * camelToSnake(\"firstName\") // \"first_name\"\n * camelToSnake(\"acceptsSmsMarketing\") // \"accepts_sms_marketing\"\n * ```\n */\nfunction camelToSnake(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/**\n * Check if a field name is a standard Rails contact field.\n */\nfunction isStandardField(fieldName: string): boolean {\n return STANDARD_FIELDS.includes(fieldName as (typeof STANDARD_FIELDS)[number]);\n}\n\n/**\n * Extract upload tokens from form values.\n * Handles both string tokens and arrays of tokens.\n */\nfunction extractUploadTokens(values: FormValues): string[] {\n const tokens: string[] = [];\n\n for (const value of Object.values(values)) {\n if (typeof value === \"string\" && value.startsWith(\"upload_\")) {\n tokens.push(value);\n } else if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item === \"string\" && item.startsWith(\"upload_\")) {\n tokens.push(item);\n }\n }\n }\n }\n\n return tokens;\n}\n\n/**\n * Format date/time values for Rails API.\n * Rails expects ISO 8601 format.\n */\nfunction formatDateForRails(value: unknown): string | undefined {\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (typeof value === \"string\") {\n // Attempt to parse and reformat to ensure valid ISO 8601\n const date = new Date(value);\n if (!isNaN(date.getTime())) {\n return date.toISOString();\n }\n }\n return undefined;\n}\n\n/**\n * Serialize form values for Rails ContactsController API.\n *\n * This function:\n * 1. Converts camelCase field names to snake_case\n * 2. Separates standard fields from custom fields\n * 3. Extracts upload tokens into contact_form_upload_tokens array\n * 4. Formats dates to ISO 8601\n * 5. Includes API configuration parameters\n *\n * @param values - Form values from useForm hook (camelCase keys)\n * @param config - Rails API configuration (apiKey, locationId, etc.)\n * @returns Serialized data ready for POST to /contacts\n *\n * @example\n * ```ts\n * const serialized = serializeForRails(\n * {\n * firstName: \"John\",\n * lastName: \"Doe\",\n * email: \"john@example.com\",\n * phone: \"555-1234\",\n * companySize: \"50-100\", // Custom field\n * resumeToken: \"upload_abc123\",\n * },\n * {\n * apiKey: \"key_123\",\n * contactCategoryToken: \"cat_xyz\",\n * locationId: \"loc_456\",\n * }\n * );\n *\n * // Result:\n * // {\n * // api_key: \"key_123\",\n * // contact_category_token: \"cat_xyz\",\n * // location_id: \"loc_456\",\n * // contact: {\n * // first_name: \"John\",\n * // last_name: \"Doe\",\n * // email: \"john@example.com\",\n * // phone: \"555-1234\",\n * // custom_fields: {\n * // company_size: \"50-100\"\n * // },\n * // contact_form_upload_tokens: [\"upload_abc123\"]\n * // }\n * // }\n * ```\n */\nexport function serializeForRails(\n values: FormValues,\n config: RailsApiConfig\n): SerializedFormData {\n const standardFields: Record<string, unknown> = {};\n const customFields: Record<string, unknown> = {};\n\n // Extract upload tokens\n const uploadTokens = extractUploadTokens(values);\n\n // Separate standard and custom fields\n for (const [key, value] of Object.entries(values)) {\n // Skip upload token fields - they're handled separately\n if (typeof value === \"string\" && value.startsWith(\"upload_\")) {\n continue;\n }\n if (Array.isArray(value) && value.every(\n (item) => typeof item === \"string\" && item.startsWith(\"upload_\")\n )) {\n continue;\n }\n\n const snakeKey = camelToSnake(key);\n\n if (isStandardField(key)) {\n // Format dates for birthday field\n if (key === \"birthday\") {\n const formatted = formatDateForRails(value);\n if (formatted) {\n standardFields[snakeKey] = formatted;\n }\n } else {\n standardFields[snakeKey] = value;\n }\n } else {\n // Custom fields\n customFields[snakeKey] = value;\n }\n }\n\n // Add config fields to standard fields if provided\n if (config.websiteId !== undefined) {\n standardFields.website_id = config.websiteId;\n }\n if (config.websiteFormAssignmentId !== undefined) {\n standardFields.website_form_assignment_id = config.websiteFormAssignmentId;\n }\n if (config.visitorIpAddress !== undefined) {\n standardFields.visitor_ip_address = config.visitorIpAddress;\n }\n\n // Build contact object\n const contact: SerializedFormData[\"contact\"] = {\n ...standardFields,\n };\n\n // Add custom fields if any\n if (Object.keys(customFields).length > 0) {\n contact.custom_fields = customFields;\n }\n\n // Add upload tokens if any\n if (uploadTokens.length > 0) {\n contact.contact_form_upload_tokens = uploadTokens;\n }\n\n // Build final serialized data\n const serialized: SerializedFormData = {\n api_key: config.apiKey,\n contact,\n };\n\n // Add optional top-level parameters\n if (config.contactCategoryToken !== undefined) {\n serialized.contact_category_token = config.contactCategoryToken;\n }\n if (config.locationId !== undefined) {\n serialized.location_id = config.locationId;\n }\n\n return serialized;\n}\n\n/**\n * Rails API error response format.\n */\nexport interface RailsErrorResponse {\n errors: {\n [field: string]: string[];\n };\n status: number;\n}\n\n/**\n * Form error format used by @page-speed/forms.\n */\nexport type FormErrors = Record<string, string | undefined>;\n\n/**\n * Convert snake_case to camelCase.\n *\n * @example\n * ```ts\n * snakeToCamel(\"first_name\") // \"firstName\"\n * snakeToCamel(\"accepts_sms_marketing\") // \"acceptsSmsMarketing\"\n * ```\n */\nfunction snakeToCamel(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/**\n * Deserialize Rails API errors to form error format.\n *\n * Converts Rails error format to the format expected by @page-speed/forms:\n * - Maps snake_case field names to camelCase\n * - Flattens error arrays to single error string (first error)\n * - Handles custom_fields errors by mapping back to original field names\n * - Extracts base errors to form-level errors\n *\n * @param railsErrors - Error response from Rails API\n * @returns Form errors object (camelCase keys)\n *\n * @example\n * ```ts\n * const formErrors = deserializeErrors({\n * errors: {\n * first_name: [\"can't be blank\", \"is too short\"],\n * email: [\"is invalid\"],\n * custom_fields: {\n * company_size: [\"is required\"]\n * },\n * base: [\"Something went wrong\"]\n * },\n * status: 422\n * });\n *\n * // Result:\n * // {\n * // firstName: \"can't be blank\",\n * // email: \"is invalid\",\n * // companySize: \"is required\",\n * // _form: \"Something went wrong\"\n * // }\n * ```\n */\nexport function deserializeErrors(railsErrors: RailsErrorResponse): FormErrors {\n const formErrors: FormErrors = {};\n\n for (const [field, messages] of Object.entries(railsErrors.errors)) {\n // Handle base errors (form-level errors)\n if (field === \"base\") {\n formErrors._form = Array.isArray(messages) ? messages[0] : messages;\n continue;\n }\n\n // Handle custom_fields errors\n if (field === \"custom_fields\" && typeof messages === \"object\") {\n for (const [customField, customMessages] of Object.entries(messages)) {\n const camelField = snakeToCamel(customField);\n const errorMessage = Array.isArray(customMessages)\n ? customMessages[0]\n : customMessages;\n formErrors[camelField] = errorMessage;\n }\n continue;\n }\n\n // Handle standard field errors\n const camelField = snakeToCamel(field);\n const errorMessage = Array.isArray(messages) ? messages[0] : messages;\n formErrors[camelField] = errorMessage;\n }\n\n return formErrors;\n}\n","/**\n * @page-speed/forms - Block Adapter\n *\n * Adapts form components for use in block-based rendering systems.\n * Wraps form components to accept block prop structure and transforms them\n * to component-native props.\n *\n * @see https://github.com/opensite-ai/page-speed-forms\n */\n\n\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Block structure for design payloads.\n * Minimal type definition for adapter compatibility.\n */\nexport interface Block {\n _id: string;\n _type: string;\n _name?: string;\n _parent?: string | null;\n tag?: string;\n styles?: string;\n content?: string;\n blockProps?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Options for Block adapter.\n */\nexport interface BlockAdapterOptions {\n /**\n * Default props to merge with block props.\n */\n defaultProps?: Record<string, unknown>;\n\n /**\n * Transform function to convert Block props to component props.\n * Useful for custom prop mapping logic.\n */\n transformProps?: (\n blockProps: Record<string, unknown>,\n block: Block,\n ) => Record<string, unknown>;\n\n /**\n * Extract display name from block for debugging.\n * Defaults to using _name or _type from block.\n */\n getDisplayName?: (block: Block) => string;\n\n /**\n * Enable React error boundary wrapping.\n * Defaults to true.\n */\n withErrorBoundary?: boolean;\n\n /**\n * Custom error fallback component.\n * If not provided, renders basic error message.\n */\n errorFallback?: (error: Error, block: Block) => React.ReactNode;\n}\n\n/**\n * Props passed to adapted component.\n */\nexport interface AdaptedComponentProps {\n /**\n * Block data from design payload.\n */\n block: Block;\n\n /**\n * Child blocks for rendering nested content.\n * Used by container components like Form.\n */\n children?: React.ReactNode;\n\n /**\n * Callback to render child blocks.\n * Provided by block rendering systems.\n */\n renderChildren?: (blockId: string) => React.ReactNode;\n}\n\n/**\n * Error boundary component for catching render errors.\n */\nclass BlockErrorBoundary extends React.Component<\n {\n block: Block;\n fallback?: (error: Error, block: Block) => React.ReactNode;\n children: React.ReactNode;\n },\n { error: Error | null }\n> {\n constructor(props: BlockErrorBoundary[\"props\"]) {\n super(props);\n this.state = { error: null };\n }\n\n static getDerivedStateFromError(error: Error) {\n return { error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n console.error(\n `Block render error (${this.props.block._id}):`,\n error,\n errorInfo,\n );\n }\n\n render() {\n if (this.state.error) {\n if (this.props.fallback) {\n return this.props.fallback(this.state.error, this.props.block);\n }\n\n return (\n <div\n className=\"block-error border border-destructive bg-destructive p-4 rounded text-destructive-foreground\"\n data-block-id={this.props.block._id}\n data-block-type={this.props.block._type}\n >\n <p className=\"font-semibold\">Block Render Error</p>\n <p className=\"text-sm\">\n Block: {this.props.block._name || this.props.block._id} (\n {this.props.block._type})\n </p>\n <p className=\"text-sm mt-1\">{this.state.error.message}</p>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n\n/**\n * Create a Block-compatible wrapper for a form component.\n *\n * This adapter transforms Block props (from design payload) into\n * component-native props. It handles:\n * - Extracting props from `blockProps` field\n * - Merging with default props\n * - Custom prop transformation\n * - Error boundary wrapping\n * - Children rendering support\n *\n * @param Component - React component to adapt\n * @param options - Adapter configuration options\n * @returns Block-compatible component\n *\n * @example\n * ```tsx\n * import { TextInput } from \"@page-speed/forms/inputs\";\n * import { createBlockAdapter } from \"@page-speed/forms/integration\";\n *\n * // Create Block-compatible TextInput\n * const BlockTextInput = createBlockAdapter(TextInput, {\n * defaultProps: {\n * placeholder: \"Enter text...\",\n * },\n * transformProps: (blockProps, block) => ({\n * ...blockProps,\n * // Map Block content to component props\n * label: block.content || blockProps.label,\n * // Apply Block styles as className\n * className: block.styles,\n * }),\n * });\n *\n * // Register with rendering system\n * registerBlockType(\"TextInput\", BlockTextInput);\n * ```\n *\n * @example\n * ```tsx\n * // Block from design payload:\n * {\n * _id: \"field-email\",\n * _type: \"TextInput\",\n * _name: \"Email Field\",\n * content: \"Email Address\",\n * styles: \"w-full mb-4\",\n * blockProps: {\n * name: \"email\",\n * type: \"email\",\n * placeholder: \"you@example.com\",\n * required: true,\n * }\n * }\n *\n * // Transformed to TextInput props:\n * <TextInput\n * name=\"email\"\n * type=\"email\"\n * placeholder=\"you@example.com\"\n * required={true}\n * label=\"Email Address\"\n * className=\"w-full mb-4\"\n * />\n * ```\n */\nexport function createBlockAdapter<TProps extends Record<string, unknown>>(\n Component: React.ComponentType<TProps>,\n options: BlockAdapterOptions = {},\n): React.ComponentType<AdaptedComponentProps> {\n const {\n defaultProps = {},\n transformProps,\n withErrorBoundary = true,\n errorFallback,\n } = options;\n\n const AdaptedComponent: React.FC<AdaptedComponentProps> = ({\n block,\n children,\n renderChildren,\n }) => {\n // Extract component props from blockProps\n const blockProps = (block.blockProps || {}) as Record<string, unknown>;\n\n // Merge with default props\n const mergedProps = { ...defaultProps, ...blockProps };\n\n // Apply custom transformation if provided\n const finalProps = transformProps\n ? transformProps(mergedProps, block)\n : mergedProps;\n\n // Add data attributes for debugging\n const dataAttrs = {\n \"data-block-id\": block._id,\n \"data-block-type\": block._type,\n ...(block._name && { \"data-block-name\": block._name }),\n };\n\n // Merge data attributes with final props\n const componentProps = {\n ...finalProps,\n ...dataAttrs,\n } as unknown as TProps;\n\n // Render children if renderChildren callback provided\n const renderedChildren = renderChildren\n ? renderChildren(block._id)\n : children;\n\n const element = (\n <Component {...componentProps}>{renderedChildren}</Component>\n );\n\n // Wrap with error boundary if enabled\n if (withErrorBoundary) {\n return (\n <BlockErrorBoundary block={block} fallback={errorFallback}>\n {element}\n </BlockErrorBoundary>\n );\n }\n\n return element;\n };\n\n // Set display name for debugging\n const componentName = Component.displayName || Component.name || \"Component\";\n AdaptedComponent.displayName = `BlockAdapter(${componentName})`;\n\n return AdaptedComponent;\n}\n\n/**\n * Standard prop transformer for form input components.\n *\n * Applies common transformations:\n * - Maps Block `content` to `label`\n * - Maps Block `styles` to `className`\n * - Preserves all blockProps\n *\n * @param blockProps - Props from Block.blockProps\n * @param block - Full Block object\n * @returns Transformed props for component\n *\n * @example\n * ```tsx\n * const BlockTextInput = createBlockAdapter(TextInput, {\n * transformProps: standardInputTransformer,\n * });\n * ```\n */\nexport function standardInputTransformer(\n blockProps: Record<string, unknown>,\n block: Block,\n): Record<string, unknown> {\n return {\n ...blockProps,\n // Use content as label if not already provided\n ...(block.content && !blockProps.label && { label: block.content }),\n // Apply Block styles as className\n ...(block.styles && { className: block.styles }),\n };\n}\n\n/**\n * Create multiple Block adapters with shared options.\n *\n * Convenience function for adapting multiple components at once\n * with the same configuration.\n *\n * @param components - Record of component name to component\n * @param options - Shared adapter options\n * @returns Record of adapted components\n *\n * @example\n * ```tsx\n * import * as Inputs from \"@page-speed/forms/inputs\";\n * import { createBlockAdapters, standardInputTransformer } from \"@page-speed/forms/integration\";\n *\n * const BlockInputs = createBlockAdapters(\n * {\n * TextInput: Inputs.TextInput,\n * TextArea: Inputs.TextArea,\n * Select: Inputs.Select,\n * },\n * {\n * transformProps: standardInputTransformer,\n * withErrorBoundary: true,\n * }\n * );\n *\n * // BlockInputs.TextInput, BlockInputs.TextArea, BlockInputs.Select\n * // are now Block-compatible\n * ```\n */\nexport function createBlockAdapters<\n TComponents extends Record<string, React.ComponentType<any>>,\n>(\n components: TComponents,\n options: BlockAdapterOptions = {},\n): Record<keyof TComponents, React.ComponentType<AdaptedComponentProps>> {\n const adapted: Record<\n string,\n React.ComponentType<AdaptedComponentProps>\n > = {};\n\n for (const [name, component] of Object.entries(components)) {\n adapted[name] = createBlockAdapter(component, options);\n }\n\n return adapted as Record<\n keyof TComponents,\n React.ComponentType<AdaptedComponentProps>\n >;\n}\n"]}
@@ -142,7 +142,11 @@ var BlockErrorBoundary = class extends React.Component {
142
142
  return { error };
143
143
  }
144
144
  componentDidCatch(error, errorInfo) {
145
- console.error(`Block render error (${this.props.block._id}):`, error, errorInfo);
145
+ console.error(
146
+ `Block render error (${this.props.block._id}):`,
147
+ error,
148
+ errorInfo
149
+ );
146
150
  }
147
151
  render() {
148
152
  if (this.state.error) {
@@ -152,7 +156,7 @@ var BlockErrorBoundary = class extends React.Component {
152
156
  return /* @__PURE__ */ React.createElement(
153
157
  "div",
154
158
  {
155
- className: "block-error border border-red-300 bg-red-50 p-4 rounded text-red-700",
159
+ className: "block-error border border-destructive bg-destructive p-4 rounded text-destructive-foreground",
156
160
  "data-block-id": this.props.block._id,
157
161
  "data-block-type": this.props.block._type
158
162
  },
@@ -171,7 +175,11 @@ function createBlockAdapter(Component2, options = {}) {
171
175
  withErrorBoundary = true,
172
176
  errorFallback
173
177
  } = options;
174
- const AdaptedComponent = ({ block, children, renderChildren }) => {
178
+ const AdaptedComponent = ({
179
+ block,
180
+ children,
181
+ renderChildren
182
+ }) => {
175
183
  const blockProps = block.blockProps || {};
176
184
  const mergedProps = { ...defaultProps, ...blockProps };
177
185
  const finalProps = transformProps ? transformProps(mergedProps, block) : mergedProps;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/integration/ContactFormSerializer.ts","../src/integration/BlockAdapter.tsx"],"names":["camelField","errorMessage","Component"],"mappings":"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","file":"integration.js","sourcesContent":["/**\n * @page-speed/forms - Rails API Serializer\n *\n * Serializes form data for the DashTrack ContactsController API.\n * Handles field name conversion (camelCase → snake_case), custom fields separation,\n * and upload token arrays.\n *\n * @see https://github.com/opensite-ai/page-speed-forms\n */\n\n/**\n * Standard fields recognized by the Rails ContactsController API.\n * These are serialized to snake_case and sent in the contact object.\n */\nconst STANDARD_FIELDS = [\n \"content\",\n \"email\",\n \"firstName\",\n \"lastName\",\n \"locationId\",\n \"phone\",\n \"subject\",\n \"redemptionStatus\",\n \"birthday\",\n \"city\",\n \"state\",\n \"websiteFormAssignmentId\",\n \"websiteId\",\n \"acceptsSmsMarketing\",\n \"acceptsEmailMarketing\",\n \"visitorIpAddress\",\n] as const;\n\n/**\n * Configuration parameters for Rails API submission.\n */\nexport interface RailsApiConfig {\n /**\n * API key for authentication.\n * Sent as top-level parameter: api_key\n */\n apiKey: string;\n\n /**\n * Contact category token for categorization.\n * Sent as top-level parameter: contact_category_token\n */\n contactCategoryToken?: string;\n\n /**\n * Location ID for multi-location organizations.\n * Sent as top-level parameter: location_id\n */\n locationId?: string;\n\n /**\n * Website ID for tracking form submissions.\n * Sent within contact object: website_id\n */\n websiteId?: string;\n\n /**\n * Website form assignment ID for form tracking.\n * Sent within contact object: website_form_assignment_id\n */\n websiteFormAssignmentId?: string;\n\n /**\n * Visitor IP address. If not provided, will be auto-detected on server.\n * Sent within contact object: visitor_ip_address\n */\n visitorIpAddress?: string;\n}\n\n/**\n * Serialized form data ready for Rails API submission.\n */\nexport interface SerializedFormData {\n /**\n * Top-level API key parameter.\n */\n api_key: string;\n\n /**\n * Top-level contact category token (optional).\n */\n contact_category_token?: string;\n\n /**\n * Top-level location ID (optional).\n */\n location_id?: string;\n\n /**\n * Contact object with standard fields and metadata.\n */\n contact: {\n /**\n * Standard contact fields in snake_case.\n */\n [key: string]: unknown;\n\n /**\n * Custom fields that don't match standard schema.\n * Stored as separate hash in Rails.\n */\n custom_fields?: Record<string, unknown>;\n\n /**\n * Array of upload tokens from file uploads.\n * These reference ContactFormUpload records in Rails.\n */\n contact_form_upload_tokens?: string[];\n };\n}\n\n/**\n * Form values from the form library (camelCase keys).\n */\nexport type FormValues = Record<string, unknown>;\n\n/**\n * Convert camelCase to snake_case.\n *\n * @example\n * ```ts\n * camelToSnake(\"firstName\") // \"first_name\"\n * camelToSnake(\"acceptsSmsMarketing\") // \"accepts_sms_marketing\"\n * ```\n */\nfunction camelToSnake(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/**\n * Check if a field name is a standard Rails contact field.\n */\nfunction isStandardField(fieldName: string): boolean {\n return STANDARD_FIELDS.includes(fieldName as (typeof STANDARD_FIELDS)[number]);\n}\n\n/**\n * Extract upload tokens from form values.\n * Handles both string tokens and arrays of tokens.\n */\nfunction extractUploadTokens(values: FormValues): string[] {\n const tokens: string[] = [];\n\n for (const value of Object.values(values)) {\n if (typeof value === \"string\" && value.startsWith(\"upload_\")) {\n tokens.push(value);\n } else if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item === \"string\" && item.startsWith(\"upload_\")) {\n tokens.push(item);\n }\n }\n }\n }\n\n return tokens;\n}\n\n/**\n * Format date/time values for Rails API.\n * Rails expects ISO 8601 format.\n */\nfunction formatDateForRails(value: unknown): string | undefined {\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (typeof value === \"string\") {\n // Attempt to parse and reformat to ensure valid ISO 8601\n const date = new Date(value);\n if (!isNaN(date.getTime())) {\n return date.toISOString();\n }\n }\n return undefined;\n}\n\n/**\n * Serialize form values for Rails ContactsController API.\n *\n * This function:\n * 1. Converts camelCase field names to snake_case\n * 2. Separates standard fields from custom fields\n * 3. Extracts upload tokens into contact_form_upload_tokens array\n * 4. Formats dates to ISO 8601\n * 5. Includes API configuration parameters\n *\n * @param values - Form values from useForm hook (camelCase keys)\n * @param config - Rails API configuration (apiKey, locationId, etc.)\n * @returns Serialized data ready for POST to /contacts\n *\n * @example\n * ```ts\n * const serialized = serializeForRails(\n * {\n * firstName: \"John\",\n * lastName: \"Doe\",\n * email: \"john@example.com\",\n * phone: \"555-1234\",\n * companySize: \"50-100\", // Custom field\n * resumeToken: \"upload_abc123\",\n * },\n * {\n * apiKey: \"key_123\",\n * contactCategoryToken: \"cat_xyz\",\n * locationId: \"loc_456\",\n * }\n * );\n *\n * // Result:\n * // {\n * // api_key: \"key_123\",\n * // contact_category_token: \"cat_xyz\",\n * // location_id: \"loc_456\",\n * // contact: {\n * // first_name: \"John\",\n * // last_name: \"Doe\",\n * // email: \"john@example.com\",\n * // phone: \"555-1234\",\n * // custom_fields: {\n * // company_size: \"50-100\"\n * // },\n * // contact_form_upload_tokens: [\"upload_abc123\"]\n * // }\n * // }\n * ```\n */\nexport function serializeForRails(\n values: FormValues,\n config: RailsApiConfig\n): SerializedFormData {\n const standardFields: Record<string, unknown> = {};\n const customFields: Record<string, unknown> = {};\n\n // Extract upload tokens\n const uploadTokens = extractUploadTokens(values);\n\n // Separate standard and custom fields\n for (const [key, value] of Object.entries(values)) {\n // Skip upload token fields - they're handled separately\n if (typeof value === \"string\" && value.startsWith(\"upload_\")) {\n continue;\n }\n if (Array.isArray(value) && value.every(\n (item) => typeof item === \"string\" && item.startsWith(\"upload_\")\n )) {\n continue;\n }\n\n const snakeKey = camelToSnake(key);\n\n if (isStandardField(key)) {\n // Format dates for birthday field\n if (key === \"birthday\") {\n const formatted = formatDateForRails(value);\n if (formatted) {\n standardFields[snakeKey] = formatted;\n }\n } else {\n standardFields[snakeKey] = value;\n }\n } else {\n // Custom fields\n customFields[snakeKey] = value;\n }\n }\n\n // Add config fields to standard fields if provided\n if (config.websiteId !== undefined) {\n standardFields.website_id = config.websiteId;\n }\n if (config.websiteFormAssignmentId !== undefined) {\n standardFields.website_form_assignment_id = config.websiteFormAssignmentId;\n }\n if (config.visitorIpAddress !== undefined) {\n standardFields.visitor_ip_address = config.visitorIpAddress;\n }\n\n // Build contact object\n const contact: SerializedFormData[\"contact\"] = {\n ...standardFields,\n };\n\n // Add custom fields if any\n if (Object.keys(customFields).length > 0) {\n contact.custom_fields = customFields;\n }\n\n // Add upload tokens if any\n if (uploadTokens.length > 0) {\n contact.contact_form_upload_tokens = uploadTokens;\n }\n\n // Build final serialized data\n const serialized: SerializedFormData = {\n api_key: config.apiKey,\n contact,\n };\n\n // Add optional top-level parameters\n if (config.contactCategoryToken !== undefined) {\n serialized.contact_category_token = config.contactCategoryToken;\n }\n if (config.locationId !== undefined) {\n serialized.location_id = config.locationId;\n }\n\n return serialized;\n}\n\n/**\n * Rails API error response format.\n */\nexport interface RailsErrorResponse {\n errors: {\n [field: string]: string[];\n };\n status: number;\n}\n\n/**\n * Form error format used by @page-speed/forms.\n */\nexport type FormErrors = Record<string, string | undefined>;\n\n/**\n * Convert snake_case to camelCase.\n *\n * @example\n * ```ts\n * snakeToCamel(\"first_name\") // \"firstName\"\n * snakeToCamel(\"accepts_sms_marketing\") // \"acceptsSmsMarketing\"\n * ```\n */\nfunction snakeToCamel(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/**\n * Deserialize Rails API errors to form error format.\n *\n * Converts Rails error format to the format expected by @page-speed/forms:\n * - Maps snake_case field names to camelCase\n * - Flattens error arrays to single error string (first error)\n * - Handles custom_fields errors by mapping back to original field names\n * - Extracts base errors to form-level errors\n *\n * @param railsErrors - Error response from Rails API\n * @returns Form errors object (camelCase keys)\n *\n * @example\n * ```ts\n * const formErrors = deserializeErrors({\n * errors: {\n * first_name: [\"can't be blank\", \"is too short\"],\n * email: [\"is invalid\"],\n * custom_fields: {\n * company_size: [\"is required\"]\n * },\n * base: [\"Something went wrong\"]\n * },\n * status: 422\n * });\n *\n * // Result:\n * // {\n * // firstName: \"can't be blank\",\n * // email: \"is invalid\",\n * // companySize: \"is required\",\n * // _form: \"Something went wrong\"\n * // }\n * ```\n */\nexport function deserializeErrors(railsErrors: RailsErrorResponse): FormErrors {\n const formErrors: FormErrors = {};\n\n for (const [field, messages] of Object.entries(railsErrors.errors)) {\n // Handle base errors (form-level errors)\n if (field === \"base\") {\n formErrors._form = Array.isArray(messages) ? messages[0] : messages;\n continue;\n }\n\n // Handle custom_fields errors\n if (field === \"custom_fields\" && typeof messages === \"object\") {\n for (const [customField, customMessages] of Object.entries(messages)) {\n const camelField = snakeToCamel(customField);\n const errorMessage = Array.isArray(customMessages)\n ? customMessages[0]\n : customMessages;\n formErrors[camelField] = errorMessage;\n }\n continue;\n }\n\n // Handle standard field errors\n const camelField = snakeToCamel(field);\n const errorMessage = Array.isArray(messages) ? messages[0] : messages;\n formErrors[camelField] = errorMessage;\n }\n\n return formErrors;\n}\n","/**\n * @page-speed/forms - Block Adapter\n *\n * Adapts form components for use in block-based rendering systems.\n * Wraps form components to accept block prop structure and transforms them\n * to component-native props.\n *\n * @see https://github.com/opensite-ai/page-speed-forms\n */\n\n\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Block structure for design payloads.\n * Minimal type definition for adapter compatibility.\n */\nexport interface Block {\n _id: string;\n _type: string;\n _name?: string;\n _parent?: string | null;\n tag?: string;\n styles?: string;\n content?: string;\n blockProps?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Options for Block adapter.\n */\nexport interface BlockAdapterOptions {\n /**\n * Default props to merge with block props.\n */\n defaultProps?: Record<string, unknown>;\n\n /**\n * Transform function to convert Block props to component props.\n * Useful for custom prop mapping logic.\n */\n transformProps?: (blockProps: Record<string, unknown>, block: Block) => Record<string, unknown>;\n\n /**\n * Extract display name from block for debugging.\n * Defaults to using _name or _type from block.\n */\n getDisplayName?: (block: Block) => string;\n\n /**\n * Enable React error boundary wrapping.\n * Defaults to true.\n */\n withErrorBoundary?: boolean;\n\n /**\n * Custom error fallback component.\n * If not provided, renders basic error message.\n */\n errorFallback?: (error: Error, block: Block) => React.ReactNode;\n}\n\n/**\n * Props passed to adapted component.\n */\nexport interface AdaptedComponentProps {\n /**\n * Block data from design payload.\n */\n block: Block;\n\n /**\n * Child blocks for rendering nested content.\n * Used by container components like Form.\n */\n children?: React.ReactNode;\n\n /**\n * Callback to render child blocks.\n * Provided by block rendering systems.\n */\n renderChildren?: (blockId: string) => React.ReactNode;\n}\n\n/**\n * Error boundary component for catching render errors.\n */\nclass BlockErrorBoundary extends React.Component<\n {\n block: Block;\n fallback?: (error: Error, block: Block) => React.ReactNode;\n children: React.ReactNode;\n },\n { error: Error | null }\n> {\n constructor(props: BlockErrorBoundary[\"props\"]) {\n super(props);\n this.state = { error: null };\n }\n\n static getDerivedStateFromError(error: Error) {\n return { error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n console.error(`Block render error (${this.props.block._id}):`, error, errorInfo);\n }\n\n render() {\n if (this.state.error) {\n if (this.props.fallback) {\n return this.props.fallback(this.state.error, this.props.block);\n }\n\n return (\n <div\n className=\"block-error border border-red-300 bg-red-50 p-4 rounded text-red-700\"\n data-block-id={this.props.block._id}\n data-block-type={this.props.block._type}\n >\n <p className=\"font-semibold\">Block Render Error</p>\n <p className=\"text-sm\">\n Block: {this.props.block._name || this.props.block._id} ({this.props.block._type})\n </p>\n <p className=\"text-sm mt-1\">{this.state.error.message}</p>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n\n/**\n * Create a Block-compatible wrapper for a form component.\n *\n * This adapter transforms Block props (from design payload) into\n * component-native props. It handles:\n * - Extracting props from `blockProps` field\n * - Merging with default props\n * - Custom prop transformation\n * - Error boundary wrapping\n * - Children rendering support\n *\n * @param Component - React component to adapt\n * @param options - Adapter configuration options\n * @returns Block-compatible component\n *\n * @example\n * ```tsx\n * import { TextInput } from \"@page-speed/forms/inputs\";\n * import { createBlockAdapter } from \"@page-speed/forms/integration\";\n *\n * // Create Block-compatible TextInput\n * const BlockTextInput = createBlockAdapter(TextInput, {\n * defaultProps: {\n * placeholder: \"Enter text...\",\n * },\n * transformProps: (blockProps, block) => ({\n * ...blockProps,\n * // Map Block content to component props\n * label: block.content || blockProps.label,\n * // Apply Block styles as className\n * className: block.styles,\n * }),\n * });\n *\n * // Register with rendering system\n * registerBlockType(\"TextInput\", BlockTextInput);\n * ```\n *\n * @example\n * ```tsx\n * // Block from design payload:\n * {\n * _id: \"field-email\",\n * _type: \"TextInput\",\n * _name: \"Email Field\",\n * content: \"Email Address\",\n * styles: \"w-full mb-4\",\n * blockProps: {\n * name: \"email\",\n * type: \"email\",\n * placeholder: \"you@example.com\",\n * required: true,\n * }\n * }\n *\n * // Transformed to TextInput props:\n * <TextInput\n * name=\"email\"\n * type=\"email\"\n * placeholder=\"you@example.com\"\n * required={true}\n * label=\"Email Address\"\n * className=\"w-full mb-4\"\n * />\n * ```\n */\nexport function createBlockAdapter<TProps extends Record<string, unknown>>(\n Component: React.ComponentType<TProps>,\n options: BlockAdapterOptions = {}\n): React.ComponentType<AdaptedComponentProps> {\n const {\n defaultProps = {},\n transformProps,\n withErrorBoundary = true,\n errorFallback,\n } = options;\n\n const AdaptedComponent: React.FC<AdaptedComponentProps> = ({ block, children, renderChildren }) => {\n // Extract component props from blockProps\n const blockProps = (block.blockProps || {}) as Record<string, unknown>;\n\n // Merge with default props\n const mergedProps = { ...defaultProps, ...blockProps };\n\n // Apply custom transformation if provided\n const finalProps = transformProps\n ? transformProps(mergedProps, block)\n : mergedProps;\n\n // Add data attributes for debugging\n const dataAttrs = {\n \"data-block-id\": block._id,\n \"data-block-type\": block._type,\n ...(block._name && { \"data-block-name\": block._name }),\n };\n\n // Merge data attributes with final props\n const componentProps = {\n ...finalProps,\n ...dataAttrs,\n } as unknown as TProps;\n\n // Render children if renderChildren callback provided\n const renderedChildren = renderChildren ? renderChildren(block._id) : children;\n\n const element = (\n <Component {...componentProps}>\n {renderedChildren}\n </Component>\n );\n\n // Wrap with error boundary if enabled\n if (withErrorBoundary) {\n return (\n <BlockErrorBoundary block={block} fallback={errorFallback}>\n {element}\n </BlockErrorBoundary>\n );\n }\n\n return element;\n };\n\n // Set display name for debugging\n const componentName = Component.displayName || Component.name || \"Component\";\n AdaptedComponent.displayName = `BlockAdapter(${componentName})`;\n\n return AdaptedComponent;\n}\n\n/**\n * Standard prop transformer for form input components.\n *\n * Applies common transformations:\n * - Maps Block `content` to `label`\n * - Maps Block `styles` to `className`\n * - Preserves all blockProps\n *\n * @param blockProps - Props from Block.blockProps\n * @param block - Full Block object\n * @returns Transformed props for component\n *\n * @example\n * ```tsx\n * const BlockTextInput = createBlockAdapter(TextInput, {\n * transformProps: standardInputTransformer,\n * });\n * ```\n */\nexport function standardInputTransformer(\n blockProps: Record<string, unknown>,\n block: Block\n): Record<string, unknown> {\n return {\n ...blockProps,\n // Use content as label if not already provided\n ...(block.content && !blockProps.label && { label: block.content }),\n // Apply Block styles as className\n ...(block.styles && { className: block.styles }),\n };\n}\n\n/**\n * Create multiple Block adapters with shared options.\n *\n * Convenience function for adapting multiple components at once\n * with the same configuration.\n *\n * @param components - Record of component name to component\n * @param options - Shared adapter options\n * @returns Record of adapted components\n *\n * @example\n * ```tsx\n * import * as Inputs from \"@page-speed/forms/inputs\";\n * import { createBlockAdapters, standardInputTransformer } from \"@page-speed/forms/integration\";\n *\n * const BlockInputs = createBlockAdapters(\n * {\n * TextInput: Inputs.TextInput,\n * TextArea: Inputs.TextArea,\n * Select: Inputs.Select,\n * },\n * {\n * transformProps: standardInputTransformer,\n * withErrorBoundary: true,\n * }\n * );\n *\n * // BlockInputs.TextInput, BlockInputs.TextArea, BlockInputs.Select\n * // are now Block-compatible\n * ```\n */\nexport function createBlockAdapters<\n TComponents extends Record<string, React.ComponentType<any>>\n>(\n components: TComponents,\n options: BlockAdapterOptions = {}\n): Record<keyof TComponents, React.ComponentType<AdaptedComponentProps>> {\n const adapted: Record<string, React.ComponentType<AdaptedComponentProps>> = {};\n\n for (const [name, component] of Object.entries(components)) {\n adapted[name] = createBlockAdapter(component, options);\n }\n\n return adapted as Record<keyof TComponents, React.ComponentType<AdaptedComponentProps>>;\n}\n"]}
1
+ {"version":3,"sources":["../src/integration/ContactFormSerializer.ts","../src/integration/BlockAdapter.tsx"],"names":["camelField","errorMessage","Component"],"mappings":"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","file":"integration.js","sourcesContent":["/**\n * @page-speed/forms - Rails API Serializer\n *\n * Serializes form data for the DashTrack ContactsController API.\n * Handles field name conversion (camelCase → snake_case), custom fields separation,\n * and upload token arrays.\n *\n * @see https://github.com/opensite-ai/page-speed-forms\n */\n\n/**\n * Standard fields recognized by the Rails ContactsController API.\n * These are serialized to snake_case and sent in the contact object.\n */\nconst STANDARD_FIELDS = [\n \"content\",\n \"email\",\n \"firstName\",\n \"lastName\",\n \"locationId\",\n \"phone\",\n \"subject\",\n \"redemptionStatus\",\n \"birthday\",\n \"city\",\n \"state\",\n \"websiteFormAssignmentId\",\n \"websiteId\",\n \"acceptsSmsMarketing\",\n \"acceptsEmailMarketing\",\n \"visitorIpAddress\",\n] as const;\n\n/**\n * Configuration parameters for Rails API submission.\n */\nexport interface RailsApiConfig {\n /**\n * API key for authentication.\n * Sent as top-level parameter: api_key\n */\n apiKey: string;\n\n /**\n * Contact category token for categorization.\n * Sent as top-level parameter: contact_category_token\n */\n contactCategoryToken?: string;\n\n /**\n * Location ID for multi-location organizations.\n * Sent as top-level parameter: location_id\n */\n locationId?: string;\n\n /**\n * Website ID for tracking form submissions.\n * Sent within contact object: website_id\n */\n websiteId?: string;\n\n /**\n * Website form assignment ID for form tracking.\n * Sent within contact object: website_form_assignment_id\n */\n websiteFormAssignmentId?: string;\n\n /**\n * Visitor IP address. If not provided, will be auto-detected on server.\n * Sent within contact object: visitor_ip_address\n */\n visitorIpAddress?: string;\n}\n\n/**\n * Serialized form data ready for Rails API submission.\n */\nexport interface SerializedFormData {\n /**\n * Top-level API key parameter.\n */\n api_key: string;\n\n /**\n * Top-level contact category token (optional).\n */\n contact_category_token?: string;\n\n /**\n * Top-level location ID (optional).\n */\n location_id?: string;\n\n /**\n * Contact object with standard fields and metadata.\n */\n contact: {\n /**\n * Standard contact fields in snake_case.\n */\n [key: string]: unknown;\n\n /**\n * Custom fields that don't match standard schema.\n * Stored as separate hash in Rails.\n */\n custom_fields?: Record<string, unknown>;\n\n /**\n * Array of upload tokens from file uploads.\n * These reference ContactFormUpload records in Rails.\n */\n contact_form_upload_tokens?: string[];\n };\n}\n\n/**\n * Form values from the form library (camelCase keys).\n */\nexport type FormValues = Record<string, unknown>;\n\n/**\n * Convert camelCase to snake_case.\n *\n * @example\n * ```ts\n * camelToSnake(\"firstName\") // \"first_name\"\n * camelToSnake(\"acceptsSmsMarketing\") // \"accepts_sms_marketing\"\n * ```\n */\nfunction camelToSnake(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/**\n * Check if a field name is a standard Rails contact field.\n */\nfunction isStandardField(fieldName: string): boolean {\n return STANDARD_FIELDS.includes(fieldName as (typeof STANDARD_FIELDS)[number]);\n}\n\n/**\n * Extract upload tokens from form values.\n * Handles both string tokens and arrays of tokens.\n */\nfunction extractUploadTokens(values: FormValues): string[] {\n const tokens: string[] = [];\n\n for (const value of Object.values(values)) {\n if (typeof value === \"string\" && value.startsWith(\"upload_\")) {\n tokens.push(value);\n } else if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item === \"string\" && item.startsWith(\"upload_\")) {\n tokens.push(item);\n }\n }\n }\n }\n\n return tokens;\n}\n\n/**\n * Format date/time values for Rails API.\n * Rails expects ISO 8601 format.\n */\nfunction formatDateForRails(value: unknown): string | undefined {\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (typeof value === \"string\") {\n // Attempt to parse and reformat to ensure valid ISO 8601\n const date = new Date(value);\n if (!isNaN(date.getTime())) {\n return date.toISOString();\n }\n }\n return undefined;\n}\n\n/**\n * Serialize form values for Rails ContactsController API.\n *\n * This function:\n * 1. Converts camelCase field names to snake_case\n * 2. Separates standard fields from custom fields\n * 3. Extracts upload tokens into contact_form_upload_tokens array\n * 4. Formats dates to ISO 8601\n * 5. Includes API configuration parameters\n *\n * @param values - Form values from useForm hook (camelCase keys)\n * @param config - Rails API configuration (apiKey, locationId, etc.)\n * @returns Serialized data ready for POST to /contacts\n *\n * @example\n * ```ts\n * const serialized = serializeForRails(\n * {\n * firstName: \"John\",\n * lastName: \"Doe\",\n * email: \"john@example.com\",\n * phone: \"555-1234\",\n * companySize: \"50-100\", // Custom field\n * resumeToken: \"upload_abc123\",\n * },\n * {\n * apiKey: \"key_123\",\n * contactCategoryToken: \"cat_xyz\",\n * locationId: \"loc_456\",\n * }\n * );\n *\n * // Result:\n * // {\n * // api_key: \"key_123\",\n * // contact_category_token: \"cat_xyz\",\n * // location_id: \"loc_456\",\n * // contact: {\n * // first_name: \"John\",\n * // last_name: \"Doe\",\n * // email: \"john@example.com\",\n * // phone: \"555-1234\",\n * // custom_fields: {\n * // company_size: \"50-100\"\n * // },\n * // contact_form_upload_tokens: [\"upload_abc123\"]\n * // }\n * // }\n * ```\n */\nexport function serializeForRails(\n values: FormValues,\n config: RailsApiConfig\n): SerializedFormData {\n const standardFields: Record<string, unknown> = {};\n const customFields: Record<string, unknown> = {};\n\n // Extract upload tokens\n const uploadTokens = extractUploadTokens(values);\n\n // Separate standard and custom fields\n for (const [key, value] of Object.entries(values)) {\n // Skip upload token fields - they're handled separately\n if (typeof value === \"string\" && value.startsWith(\"upload_\")) {\n continue;\n }\n if (Array.isArray(value) && value.every(\n (item) => typeof item === \"string\" && item.startsWith(\"upload_\")\n )) {\n continue;\n }\n\n const snakeKey = camelToSnake(key);\n\n if (isStandardField(key)) {\n // Format dates for birthday field\n if (key === \"birthday\") {\n const formatted = formatDateForRails(value);\n if (formatted) {\n standardFields[snakeKey] = formatted;\n }\n } else {\n standardFields[snakeKey] = value;\n }\n } else {\n // Custom fields\n customFields[snakeKey] = value;\n }\n }\n\n // Add config fields to standard fields if provided\n if (config.websiteId !== undefined) {\n standardFields.website_id = config.websiteId;\n }\n if (config.websiteFormAssignmentId !== undefined) {\n standardFields.website_form_assignment_id = config.websiteFormAssignmentId;\n }\n if (config.visitorIpAddress !== undefined) {\n standardFields.visitor_ip_address = config.visitorIpAddress;\n }\n\n // Build contact object\n const contact: SerializedFormData[\"contact\"] = {\n ...standardFields,\n };\n\n // Add custom fields if any\n if (Object.keys(customFields).length > 0) {\n contact.custom_fields = customFields;\n }\n\n // Add upload tokens if any\n if (uploadTokens.length > 0) {\n contact.contact_form_upload_tokens = uploadTokens;\n }\n\n // Build final serialized data\n const serialized: SerializedFormData = {\n api_key: config.apiKey,\n contact,\n };\n\n // Add optional top-level parameters\n if (config.contactCategoryToken !== undefined) {\n serialized.contact_category_token = config.contactCategoryToken;\n }\n if (config.locationId !== undefined) {\n serialized.location_id = config.locationId;\n }\n\n return serialized;\n}\n\n/**\n * Rails API error response format.\n */\nexport interface RailsErrorResponse {\n errors: {\n [field: string]: string[];\n };\n status: number;\n}\n\n/**\n * Form error format used by @page-speed/forms.\n */\nexport type FormErrors = Record<string, string | undefined>;\n\n/**\n * Convert snake_case to camelCase.\n *\n * @example\n * ```ts\n * snakeToCamel(\"first_name\") // \"firstName\"\n * snakeToCamel(\"accepts_sms_marketing\") // \"acceptsSmsMarketing\"\n * ```\n */\nfunction snakeToCamel(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/**\n * Deserialize Rails API errors to form error format.\n *\n * Converts Rails error format to the format expected by @page-speed/forms:\n * - Maps snake_case field names to camelCase\n * - Flattens error arrays to single error string (first error)\n * - Handles custom_fields errors by mapping back to original field names\n * - Extracts base errors to form-level errors\n *\n * @param railsErrors - Error response from Rails API\n * @returns Form errors object (camelCase keys)\n *\n * @example\n * ```ts\n * const formErrors = deserializeErrors({\n * errors: {\n * first_name: [\"can't be blank\", \"is too short\"],\n * email: [\"is invalid\"],\n * custom_fields: {\n * company_size: [\"is required\"]\n * },\n * base: [\"Something went wrong\"]\n * },\n * status: 422\n * });\n *\n * // Result:\n * // {\n * // firstName: \"can't be blank\",\n * // email: \"is invalid\",\n * // companySize: \"is required\",\n * // _form: \"Something went wrong\"\n * // }\n * ```\n */\nexport function deserializeErrors(railsErrors: RailsErrorResponse): FormErrors {\n const formErrors: FormErrors = {};\n\n for (const [field, messages] of Object.entries(railsErrors.errors)) {\n // Handle base errors (form-level errors)\n if (field === \"base\") {\n formErrors._form = Array.isArray(messages) ? messages[0] : messages;\n continue;\n }\n\n // Handle custom_fields errors\n if (field === \"custom_fields\" && typeof messages === \"object\") {\n for (const [customField, customMessages] of Object.entries(messages)) {\n const camelField = snakeToCamel(customField);\n const errorMessage = Array.isArray(customMessages)\n ? customMessages[0]\n : customMessages;\n formErrors[camelField] = errorMessage;\n }\n continue;\n }\n\n // Handle standard field errors\n const camelField = snakeToCamel(field);\n const errorMessage = Array.isArray(messages) ? messages[0] : messages;\n formErrors[camelField] = errorMessage;\n }\n\n return formErrors;\n}\n","/**\n * @page-speed/forms - Block Adapter\n *\n * Adapts form components for use in block-based rendering systems.\n * Wraps form components to accept block prop structure and transforms them\n * to component-native props.\n *\n * @see https://github.com/opensite-ai/page-speed-forms\n */\n\n\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Block structure for design payloads.\n * Minimal type definition for adapter compatibility.\n */\nexport interface Block {\n _id: string;\n _type: string;\n _name?: string;\n _parent?: string | null;\n tag?: string;\n styles?: string;\n content?: string;\n blockProps?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Options for Block adapter.\n */\nexport interface BlockAdapterOptions {\n /**\n * Default props to merge with block props.\n */\n defaultProps?: Record<string, unknown>;\n\n /**\n * Transform function to convert Block props to component props.\n * Useful for custom prop mapping logic.\n */\n transformProps?: (\n blockProps: Record<string, unknown>,\n block: Block,\n ) => Record<string, unknown>;\n\n /**\n * Extract display name from block for debugging.\n * Defaults to using _name or _type from block.\n */\n getDisplayName?: (block: Block) => string;\n\n /**\n * Enable React error boundary wrapping.\n * Defaults to true.\n */\n withErrorBoundary?: boolean;\n\n /**\n * Custom error fallback component.\n * If not provided, renders basic error message.\n */\n errorFallback?: (error: Error, block: Block) => React.ReactNode;\n}\n\n/**\n * Props passed to adapted component.\n */\nexport interface AdaptedComponentProps {\n /**\n * Block data from design payload.\n */\n block: Block;\n\n /**\n * Child blocks for rendering nested content.\n * Used by container components like Form.\n */\n children?: React.ReactNode;\n\n /**\n * Callback to render child blocks.\n * Provided by block rendering systems.\n */\n renderChildren?: (blockId: string) => React.ReactNode;\n}\n\n/**\n * Error boundary component for catching render errors.\n */\nclass BlockErrorBoundary extends React.Component<\n {\n block: Block;\n fallback?: (error: Error, block: Block) => React.ReactNode;\n children: React.ReactNode;\n },\n { error: Error | null }\n> {\n constructor(props: BlockErrorBoundary[\"props\"]) {\n super(props);\n this.state = { error: null };\n }\n\n static getDerivedStateFromError(error: Error) {\n return { error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n console.error(\n `Block render error (${this.props.block._id}):`,\n error,\n errorInfo,\n );\n }\n\n render() {\n if (this.state.error) {\n if (this.props.fallback) {\n return this.props.fallback(this.state.error, this.props.block);\n }\n\n return (\n <div\n className=\"block-error border border-destructive bg-destructive p-4 rounded text-destructive-foreground\"\n data-block-id={this.props.block._id}\n data-block-type={this.props.block._type}\n >\n <p className=\"font-semibold\">Block Render Error</p>\n <p className=\"text-sm\">\n Block: {this.props.block._name || this.props.block._id} (\n {this.props.block._type})\n </p>\n <p className=\"text-sm mt-1\">{this.state.error.message}</p>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n\n/**\n * Create a Block-compatible wrapper for a form component.\n *\n * This adapter transforms Block props (from design payload) into\n * component-native props. It handles:\n * - Extracting props from `blockProps` field\n * - Merging with default props\n * - Custom prop transformation\n * - Error boundary wrapping\n * - Children rendering support\n *\n * @param Component - React component to adapt\n * @param options - Adapter configuration options\n * @returns Block-compatible component\n *\n * @example\n * ```tsx\n * import { TextInput } from \"@page-speed/forms/inputs\";\n * import { createBlockAdapter } from \"@page-speed/forms/integration\";\n *\n * // Create Block-compatible TextInput\n * const BlockTextInput = createBlockAdapter(TextInput, {\n * defaultProps: {\n * placeholder: \"Enter text...\",\n * },\n * transformProps: (blockProps, block) => ({\n * ...blockProps,\n * // Map Block content to component props\n * label: block.content || blockProps.label,\n * // Apply Block styles as className\n * className: block.styles,\n * }),\n * });\n *\n * // Register with rendering system\n * registerBlockType(\"TextInput\", BlockTextInput);\n * ```\n *\n * @example\n * ```tsx\n * // Block from design payload:\n * {\n * _id: \"field-email\",\n * _type: \"TextInput\",\n * _name: \"Email Field\",\n * content: \"Email Address\",\n * styles: \"w-full mb-4\",\n * blockProps: {\n * name: \"email\",\n * type: \"email\",\n * placeholder: \"you@example.com\",\n * required: true,\n * }\n * }\n *\n * // Transformed to TextInput props:\n * <TextInput\n * name=\"email\"\n * type=\"email\"\n * placeholder=\"you@example.com\"\n * required={true}\n * label=\"Email Address\"\n * className=\"w-full mb-4\"\n * />\n * ```\n */\nexport function createBlockAdapter<TProps extends Record<string, unknown>>(\n Component: React.ComponentType<TProps>,\n options: BlockAdapterOptions = {},\n): React.ComponentType<AdaptedComponentProps> {\n const {\n defaultProps = {},\n transformProps,\n withErrorBoundary = true,\n errorFallback,\n } = options;\n\n const AdaptedComponent: React.FC<AdaptedComponentProps> = ({\n block,\n children,\n renderChildren,\n }) => {\n // Extract component props from blockProps\n const blockProps = (block.blockProps || {}) as Record<string, unknown>;\n\n // Merge with default props\n const mergedProps = { ...defaultProps, ...blockProps };\n\n // Apply custom transformation if provided\n const finalProps = transformProps\n ? transformProps(mergedProps, block)\n : mergedProps;\n\n // Add data attributes for debugging\n const dataAttrs = {\n \"data-block-id\": block._id,\n \"data-block-type\": block._type,\n ...(block._name && { \"data-block-name\": block._name }),\n };\n\n // Merge data attributes with final props\n const componentProps = {\n ...finalProps,\n ...dataAttrs,\n } as unknown as TProps;\n\n // Render children if renderChildren callback provided\n const renderedChildren = renderChildren\n ? renderChildren(block._id)\n : children;\n\n const element = (\n <Component {...componentProps}>{renderedChildren}</Component>\n );\n\n // Wrap with error boundary if enabled\n if (withErrorBoundary) {\n return (\n <BlockErrorBoundary block={block} fallback={errorFallback}>\n {element}\n </BlockErrorBoundary>\n );\n }\n\n return element;\n };\n\n // Set display name for debugging\n const componentName = Component.displayName || Component.name || \"Component\";\n AdaptedComponent.displayName = `BlockAdapter(${componentName})`;\n\n return AdaptedComponent;\n}\n\n/**\n * Standard prop transformer for form input components.\n *\n * Applies common transformations:\n * - Maps Block `content` to `label`\n * - Maps Block `styles` to `className`\n * - Preserves all blockProps\n *\n * @param blockProps - Props from Block.blockProps\n * @param block - Full Block object\n * @returns Transformed props for component\n *\n * @example\n * ```tsx\n * const BlockTextInput = createBlockAdapter(TextInput, {\n * transformProps: standardInputTransformer,\n * });\n * ```\n */\nexport function standardInputTransformer(\n blockProps: Record<string, unknown>,\n block: Block,\n): Record<string, unknown> {\n return {\n ...blockProps,\n // Use content as label if not already provided\n ...(block.content && !blockProps.label && { label: block.content }),\n // Apply Block styles as className\n ...(block.styles && { className: block.styles }),\n };\n}\n\n/**\n * Create multiple Block adapters with shared options.\n *\n * Convenience function for adapting multiple components at once\n * with the same configuration.\n *\n * @param components - Record of component name to component\n * @param options - Shared adapter options\n * @returns Record of adapted components\n *\n * @example\n * ```tsx\n * import * as Inputs from \"@page-speed/forms/inputs\";\n * import { createBlockAdapters, standardInputTransformer } from \"@page-speed/forms/integration\";\n *\n * const BlockInputs = createBlockAdapters(\n * {\n * TextInput: Inputs.TextInput,\n * TextArea: Inputs.TextArea,\n * Select: Inputs.Select,\n * },\n * {\n * transformProps: standardInputTransformer,\n * withErrorBoundary: true,\n * }\n * );\n *\n * // BlockInputs.TextInput, BlockInputs.TextArea, BlockInputs.Select\n * // are now Block-compatible\n * ```\n */\nexport function createBlockAdapters<\n TComponents extends Record<string, React.ComponentType<any>>,\n>(\n components: TComponents,\n options: BlockAdapterOptions = {},\n): Record<keyof TComponents, React.ComponentType<AdaptedComponentProps>> {\n const adapted: Record<\n string,\n React.ComponentType<AdaptedComponentProps>\n > = {};\n\n for (const [name, component] of Object.entries(components)) {\n adapted[name] = createBlockAdapter(component, options);\n }\n\n return adapted as Record<\n keyof TComponents,\n React.ComponentType<AdaptedComponentProps>\n >;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@page-speed/forms",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Ultra-high-performance React form library with field-level reactivity and tree-shakable architecture",
5
5
  "keywords": [
6
6
  "react",