{"version":3,"file":"data-binding.cjs","sources":["../../../../../src/lib/data-binding.ts"],"sourcesContent":["/**\r\n * Data Binding System\r\n * \r\n * Functions for parsing and resolving data binding expressions.\r\n * Supports expressions like {{path.to.value}} and {{items[0].name}}.\r\n * \r\n * Requirements: 5.4\r\n */\r\n\r\nimport type { DataContext } from '../types';\r\n\r\n/**\r\n * Result of resolving a binding expression\r\n */\r\nexport interface BindingResult {\r\n  /** Whether the binding was successfully resolved */\r\n  success: boolean;\r\n  /** The resolved value (if successful) */\r\n  value?: unknown;\r\n  /** Error message (if failed) */\r\n  error?: string;\r\n}\r\n\r\n/**\r\n * Parsed path segment\r\n */\r\nexport type PathSegment =\r\n  | { type: 'property'; name: string }\r\n  | { type: 'index'; index: number };\r\n\r\n/**\r\n * Result of parsing a binding expression\r\n */\r\nexport interface ParseResult {\r\n  /** Whether parsing was successful */\r\n  success: boolean;\r\n  /** The parsed path segments (if successful) */\r\n  segments?: PathSegment[];\r\n  /** The original expression without braces */\r\n  expression?: string;\r\n  /** Error message (if failed) */\r\n  error?: string;\r\n}\r\n\r\n/**\r\n * Regular expression to match binding expressions: {{...}}\r\n */\r\nconst BINDING_PATTERN = /\\{\\{([^}]+)\\}\\}/g;\r\n\r\n/**\r\n * Regular expression to match a single binding expression\r\n */\r\nconst SINGLE_BINDING_PATTERN = /^\\{\\{([^}]+)\\}\\}$/;\r\n\r\n/**\r\n * Parse a path expression into segments\r\n * Supports dot notation (a.b.c) and bracket notation (a[0].b)\r\n * \r\n * @param expression - The path expression (without {{ }})\r\n * @returns Array of path segments\r\n */\r\nexport function parsePath(expression: string): ParseResult {\r\n  if (!expression || expression.trim() === '') {\r\n    return {\r\n      success: false,\r\n      error: 'Empty expression',\r\n    };\r\n  }\r\n\r\n  const trimmed = expression.trim();\r\n  const segments: PathSegment[] = [];\r\n  let current = '';\r\n  let i = 0;\r\n\r\n  while (i < trimmed.length) {\r\n    const char = trimmed[i];\r\n\r\n    if (char === '.') {\r\n      // Dot separator - push current property if exists\r\n      if (current) {\r\n        segments.push({ type: 'property', name: current });\r\n        current = '';\r\n      } else if (segments.length === 0) {\r\n        // Leading dot is invalid\r\n        return {\r\n          success: false,\r\n          error: 'Invalid expression: leading dot',\r\n        };\r\n      }\r\n      i++;\r\n    } else if (char === '[') {\r\n      // Start of bracket notation\r\n      if (current) {\r\n        segments.push({ type: 'property', name: current });\r\n        current = '';\r\n      }\r\n\r\n      // Find closing bracket\r\n      const closeBracket = trimmed.indexOf(']', i);\r\n      if (closeBracket === -1) {\r\n        return {\r\n          success: false,\r\n          error: 'Invalid expression: unclosed bracket',\r\n        };\r\n      }\r\n\r\n      const indexStr = trimmed.substring(i + 1, closeBracket);\r\n      const index = parseInt(indexStr, 10);\r\n\r\n      if (isNaN(index) || index < 0) {\r\n        return {\r\n          success: false,\r\n          error: `Invalid array index: ${indexStr}`,\r\n        };\r\n      }\r\n\r\n      segments.push({ type: 'index', index });\r\n      i = closeBracket + 1;\r\n    } else {\r\n      // Regular character - add to current property name\r\n      current += char;\r\n      i++;\r\n    }\r\n  }\r\n\r\n  // Push final segment if exists\r\n  if (current) {\r\n    segments.push({ type: 'property', name: current });\r\n  }\r\n\r\n  if (segments.length === 0) {\r\n    return {\r\n      success: false,\r\n      error: 'Empty path expression',\r\n    };\r\n  }\r\n\r\n  return {\r\n    success: true,\r\n    segments,\r\n    expression: trimmed,\r\n  };\r\n}\r\n\r\n\r\n/**\r\n * Parse a binding expression (with {{ }} delimiters)\r\n * \r\n * @param binding - The binding expression (e.g., \"{{user.name}}\")\r\n * @returns ParseResult with path segments or error\r\n */\r\nexport function parseBindingExpression(binding: string): ParseResult {\r\n  if (!binding || binding.trim() === '') {\r\n    return {\r\n      success: false,\r\n      error: 'Empty binding expression',\r\n    };\r\n  }\r\n\r\n  const match = binding.match(SINGLE_BINDING_PATTERN);\r\n  if (!match) {\r\n    return {\r\n      success: false,\r\n      error: 'Invalid binding format: must be {{expression}}',\r\n    };\r\n  }\r\n\r\n  return parsePath(match[1]);\r\n}\r\n\r\n/**\r\n * Resolve a path against a data context\r\n * \r\n * @param segments - The parsed path segments\r\n * @param data - The data context to resolve against\r\n * @returns The resolved value or undefined if path doesn't exist\r\n */\r\nexport function resolvePath(segments: PathSegment[], data: DataContext): BindingResult {\r\n  let current: unknown = data;\r\n\r\n  for (const segment of segments) {\r\n    if (current === null || current === undefined) {\r\n      return {\r\n        success: false,\r\n        error: `Cannot access property of ${current}`,\r\n      };\r\n    }\r\n\r\n    if (segment.type === 'property') {\r\n      if (typeof current !== 'object') {\r\n        return {\r\n          success: false,\r\n          error: `Cannot access property \"${segment.name}\" of non-object`,\r\n        };\r\n      }\r\n      current = (current as Record<string, unknown>)[segment.name];\r\n    } else {\r\n      // index access\r\n      if (!Array.isArray(current)) {\r\n        return {\r\n          success: false,\r\n          error: `Cannot access index ${segment.index} of non-array`,\r\n        };\r\n      }\r\n      if (segment.index >= current.length) {\r\n        return {\r\n          success: false,\r\n          error: `Array index ${segment.index} out of bounds (length: ${current.length})`,\r\n        };\r\n      }\r\n      current = current[segment.index];\r\n    }\r\n  }\r\n\r\n  return {\r\n    success: true,\r\n    value: current,\r\n  };\r\n}\r\n\r\n/**\r\n * Resolve a single binding expression against a data context\r\n * \r\n * @param binding - The binding expression (e.g., \"{{user.name}}\")\r\n * @param data - The data context\r\n * @returns BindingResult with resolved value or error\r\n */\r\nexport function resolveBinding(binding: string, data: DataContext): BindingResult {\r\n  const parseResult = parseBindingExpression(binding);\r\n  if (!parseResult.success || !parseResult.segments) {\r\n    return {\r\n      success: false,\r\n      error: parseResult.error,\r\n    };\r\n  }\r\n\r\n  return resolvePath(parseResult.segments, data);\r\n}\r\n\r\n/**\r\n * Resolve all binding expressions in a string\r\n * Replaces {{...}} expressions with their resolved values\r\n * \r\n * @param template - The template string with binding expressions\r\n * @param data - The data context\r\n * @returns The resolved string with bindings replaced\r\n */\r\nexport function resolveBindings(template: string, data: DataContext): string {\r\n  if (!template) {\r\n    return template;\r\n  }\r\n\r\n  return template.replace(BINDING_PATTERN, (match, expression) => {\r\n    const parseResult = parsePath(expression);\r\n    if (!parseResult.success || !parseResult.segments) {\r\n      return match; // Keep original if parsing fails\r\n    }\r\n\r\n    const resolveResult = resolvePath(parseResult.segments, data);\r\n    if (!resolveResult.success) {\r\n      return match; // Keep original if resolution fails\r\n    }\r\n\r\n    // Convert value to string\r\n    const value = resolveResult.value;\r\n    if (value === null || value === undefined) {\r\n      return '';\r\n    }\r\n    if (typeof value === 'object') {\r\n      return JSON.stringify(value);\r\n    }\r\n    return String(value);\r\n  });\r\n}\r\n\r\n\r\n/**\r\n * Data field information extracted from a UISchema\r\n */\r\nexport interface DataField {\r\n  /** The binding expression (e.g., \"{{user.name}}\") */\r\n  binding: string;\r\n  /** The path expression without braces (e.g., \"user.name\") */\r\n  path: string;\r\n  /** The parsed path segments */\r\n  segments: PathSegment[];\r\n  /** The component ID where this binding was found */\r\n  componentId: string;\r\n  /** The property where this binding was found (e.g., \"text\", \"binding\", \"props.label\") */\r\n  property: string;\r\n}\r\n\r\n/**\r\n * Import UIComponent and UISchema for extraction\r\n */\r\nimport type { UIComponent, UISchema } from '../types';\r\n\r\n/**\r\n * Extract all data binding expressions from a UIComponent recursively\r\n * \r\n * @param component - The component to extract bindings from\r\n * @param fields - Array to collect found fields\r\n * @param propertyPrefix - Prefix for nested property paths\r\n */\r\nfunction extractFromComponent(\r\n  component: UIComponent,\r\n  fields: DataField[],\r\n  propertyPrefix: string = ''\r\n): void {\r\n  const componentId = component.id;\r\n\r\n  // Check direct binding property\r\n  if (component.binding) {\r\n    const parseResult = parseBindingExpression(component.binding);\r\n    if (parseResult.success && parseResult.segments && parseResult.expression) {\r\n      fields.push({\r\n        binding: component.binding,\r\n        path: parseResult.expression,\r\n        segments: parseResult.segments,\r\n        componentId,\r\n        property: propertyPrefix ? `${propertyPrefix}.binding` : 'binding',\r\n      });\r\n    }\r\n  }\r\n\r\n  // Check text property for bindings\r\n  if (component.text) {\r\n    extractBindingsFromString(\r\n      component.text,\r\n      componentId,\r\n      propertyPrefix ? `${propertyPrefix}.text` : 'text',\r\n      fields\r\n    );\r\n  }\r\n\r\n  // Check props for bindings\r\n  if (component.props) {\r\n    extractBindingsFromObject(\r\n      component.props,\r\n      componentId,\r\n      propertyPrefix ? `${propertyPrefix}.props` : 'props',\r\n      fields\r\n    );\r\n  }\r\n\r\n  // Check loop source\r\n  if (component.loop?.source) {\r\n    const parseResult = parsePath(component.loop.source);\r\n    if (parseResult.success && parseResult.segments) {\r\n      fields.push({\r\n        binding: `{{${component.loop.source}}}`,\r\n        path: component.loop.source,\r\n        segments: parseResult.segments,\r\n        componentId,\r\n        property: propertyPrefix ? `${propertyPrefix}.loop.source` : 'loop.source',\r\n      });\r\n    }\r\n  }\r\n\r\n  // Check condition for bindings\r\n  if (component.condition) {\r\n    extractBindingsFromString(\r\n      component.condition,\r\n      componentId,\r\n      propertyPrefix ? `${propertyPrefix}.condition` : 'condition',\r\n      fields\r\n    );\r\n  }\r\n\r\n  // Recursively process children\r\n  if (component.children) {\r\n    component.children.forEach((child, index) => {\r\n      extractFromComponent(child, fields, `children[${index}]`);\r\n    });\r\n  }\r\n}\r\n\r\n/**\r\n * Extract bindings from a string value\r\n */\r\nfunction extractBindingsFromString(\r\n  value: string,\r\n  componentId: string,\r\n  property: string,\r\n  fields: DataField[]\r\n): void {\r\n  const bindingPattern = /\\{\\{([^}]+)\\}\\}/g;\r\n  let match;\r\n\r\n  while ((match = bindingPattern.exec(value)) !== null) {\r\n    const expression = match[1];\r\n    const parseResult = parsePath(expression);\r\n    if (parseResult.success && parseResult.segments) {\r\n      fields.push({\r\n        binding: match[0],\r\n        path: expression,\r\n        segments: parseResult.segments,\r\n        componentId,\r\n        property,\r\n      });\r\n    }\r\n  }\r\n}\r\n\r\n/**\r\n * Extract bindings from an object (props)\r\n */\r\nfunction extractBindingsFromObject(\r\n  obj: Record<string, unknown>,\r\n  componentId: string,\r\n  propertyPrefix: string,\r\n  fields: DataField[]\r\n): void {\r\n  for (const [key, value] of Object.entries(obj)) {\r\n    const property = `${propertyPrefix}.${key}`;\r\n    \r\n    if (typeof value === 'string') {\r\n      extractBindingsFromString(value, componentId, property, fields);\r\n    } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\r\n      extractBindingsFromObject(\r\n        value as Record<string, unknown>,\r\n        componentId,\r\n        property,\r\n        fields\r\n      );\r\n    } else if (Array.isArray(value)) {\r\n      value.forEach((item, index) => {\r\n        if (typeof item === 'string') {\r\n          extractBindingsFromString(item, componentId, `${property}[${index}]`, fields);\r\n        } else if (typeof item === 'object' && item !== null) {\r\n          extractBindingsFromObject(\r\n            item as Record<string, unknown>,\r\n            componentId,\r\n            `${property}[${index}]`,\r\n            fields\r\n          );\r\n        }\r\n      });\r\n    }\r\n  }\r\n}\r\n\r\n/**\r\n * Extract all data binding fields from a UISchema\r\n * \r\n * @param schema - The UISchema to extract bindings from\r\n * @returns Array of DataField objects representing all bindings found\r\n */\r\nexport function extractDataFields(schema: UISchema): DataField[] {\r\n  const fields: DataField[] = [];\r\n  extractFromComponent(schema.root, fields);\r\n  return fields;\r\n}\r\n\r\n/**\r\n * Get unique binding paths from a UISchema\r\n * \r\n * @param schema - The UISchema to extract paths from\r\n * @returns Array of unique path strings\r\n */\r\nexport function getUniquePaths(schema: UISchema): string[] {\r\n  const fields = extractDataFields(schema);\r\n  const paths = new Set(fields.map(f => f.path));\r\n  return Array.from(paths);\r\n}\r\n"],"names":["BINDING_PATTERN","SINGLE_BINDING_PATTERN","parsePath","expression","trimmed","segments","current","i","char","closeBracket","indexStr","index","parseBindingExpression","binding","match","resolvePath","data","segment","resolveBinding","parseResult","resolveBindings","template","resolveResult","value","extractFromComponent","component","fields","propertyPrefix","componentId","extractBindingsFromString","extractBindingsFromObject","child","property","bindingPattern","obj","key","item","extractDataFields","schema"],"mappings":"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"}