import React, { useMemo } from 'react'; import { View, ImageBackground, StyleSheet, type ViewStyle } from 'react-native'; import type { TContainer, TComponent } from '@namiml/sdk-core'; import { applyStyles, parseColor, resolveFillImageUrl } from '../../utils/styles'; import { resolveOverlayColor } from '../../utils/rendering'; import { usePaywallContext } from '../../context/PaywallContext'; import { TemplateRenderer } from '../TemplateRenderer'; import { BackgroundOverlay } from './BackgroundOverlay'; interface Props { component: TContainer; scaleFactor: number; onClose?: () => void; } export const NamiBackgroundContainer: React.FC = ({ component, scaleFactor, onClose }) => { const ctx = usePaywallContext(); const containerStyle = useMemo( () => (component ? (applyStyles(component as any, scaleFactor) as ViewStyle) : {}), [component, scaleFactor] ); // NAM-3538: only overlayColor is resolved here; the container itself skips TemplateRenderer. const overlayColor = useMemo(() => resolveOverlayColor(ctx, component), [ctx, component]); if (!component || component.hidden) return null; const fillImageUrl = resolveFillImageUrl(component.fillImage); const bgColor = parseColor(component.fillColor) ?? parseColor(component.fillColorFallback); // Only clip when the container is a filled "tile" with specific (non-all-corners) // rounding (NAM-1798); all-corners / unrounded filled containers stay unclipped so // NC-3948 / NAM-1201 over-rounding is not reintroduced. Matches the Apple/Web gate. const roundBorders = component.roundBorders; const hasSpecificCorners = Array.isArray(roundBorders) && roundBorders.length > 0 && roundBorders.length < 4; const clipStyle: ViewStyle = (component.fillColor || component.fillImage) && hasSpecificCorners ? { overflow: 'hidden' } : {}; const direction = component.direction ?? 'vertical'; const content = ( <> {component.components?.map((comp: TComponent, i: number) => { const marginKey = direction === 'vertical' ? 'topMargin' : 'leftMargin'; const spacedComponent = i === 0 || !component.spacing ? comp : ({ ...comp, [marginKey]: mergeRawMarginValue((comp as any)[marginKey], component.spacing), } as TComponent); return ( ); })} ); const overlay = overlayColor ? : null; if (fillImageUrl) { return ( {content} {overlay} ); } return ( {content} {overlay} ); }; function mergeRawMarginValue(existing: unknown, spacing: unknown): unknown { if (existing == null) return spacing; const existingNumeric = toNumericMargin(existing); const spacingNumeric = toNumericMargin(spacing); if (existingNumeric == null || spacingNumeric == null) { return existing; } return existingNumeric + spacingNumeric; } function toNumericMargin(value: unknown): number | null { if (typeof value === 'number' && Number.isFinite(value)) { return value; } if (typeof value !== 'string') { return null; } const trimmed = value.trim(); if (!trimmed || !/^-?\d+(\.\d+)?$/.test(trimmed)) { return null; } const parsed = Number(trimmed); return Number.isFinite(parsed) ? parsed : null; } const styles = StyleSheet.create({ background: { ...StyleSheet.absoluteFillObject, }, });