@namiml/expo-sdk 3.4.12-dev.202610052353 → 3.4.12-dev.202610060208

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2335,6 +2335,117 @@ function isLinearGradient(value) {
2335
2335
  return false;
2336
2336
  return value.trim().toLowerCase().startsWith('linear-gradient(');
2337
2337
  }
2338
+ function splitGradientArgs(input) {
2339
+ const out = [];
2340
+ let current = '';
2341
+ let depth = 0;
2342
+ for (let i = 0; i < input.length; i++) {
2343
+ const ch = input[i];
2344
+ if (ch === '(')
2345
+ depth += 1;
2346
+ if (ch === ')')
2347
+ depth = Math.max(0, depth - 1);
2348
+ if (ch === ',' && depth === 0) {
2349
+ out.push(current.trim());
2350
+ current = '';
2351
+ continue;
2352
+ }
2353
+ current += ch;
2354
+ }
2355
+ if (current.trim())
2356
+ out.push(current.trim());
2357
+ return out;
2358
+ }
2359
+ function parseAngleToken(token) {
2360
+ const t = token.trim().toLowerCase();
2361
+ if (t.endsWith('deg')) {
2362
+ const num = parseFloat(t.replace('deg', '').trim());
2363
+ return Number.isFinite(num) ? num : undefined;
2364
+ }
2365
+ if (t.startsWith('to ')) {
2366
+ // CSS gradient directions
2367
+ if (t.includes('right') && t.includes('top'))
2368
+ return 45;
2369
+ if (t.includes('right') && t.includes('bottom'))
2370
+ return 135;
2371
+ if (t.includes('left') && t.includes('bottom'))
2372
+ return 225;
2373
+ if (t.includes('left') && t.includes('top'))
2374
+ return 315;
2375
+ if (t.includes('right'))
2376
+ return 90;
2377
+ if (t.includes('left'))
2378
+ return 270;
2379
+ if (t.includes('bottom'))
2380
+ return 180;
2381
+ if (t.includes('top'))
2382
+ return 0;
2383
+ }
2384
+ return undefined;
2385
+ }
2386
+ // NAM-3538: parse `linear-gradient(<angle|to side>?, <color> [pct%], ...)` for the
2387
+ // backgroundContainer overlay. Offsets are 0..1; stops without a % are spaced evenly.
2388
+ function parseLinearGradient(value) {
2389
+ if (!isLinearGradient(value))
2390
+ return undefined;
2391
+ const trimmed = value.trim();
2392
+ const open = trimmed.indexOf('(');
2393
+ const close = trimmed.lastIndexOf(')');
2394
+ if (open < 0 || close <= open)
2395
+ return undefined;
2396
+ const args = splitGradientArgs(trimmed.slice(open + 1, close));
2397
+ if (!args.length)
2398
+ return undefined;
2399
+ let angle = 180;
2400
+ const firstAngle = parseAngleToken(args[0]);
2401
+ if (firstAngle !== undefined) {
2402
+ angle = firstAngle;
2403
+ args.shift();
2404
+ }
2405
+ const colors = [];
2406
+ const offsets = [];
2407
+ for (const arg of args) {
2408
+ const match = arg.match(/^(.*?)(?:\s+(-?[\d.]+)%)?$/);
2409
+ const color = parseColor((match?.[1] ?? arg).trim());
2410
+ if (!color)
2411
+ return undefined;
2412
+ colors.push(color);
2413
+ const pct = match?.[2] !== undefined ? parseFloat(match[2]) : NaN;
2414
+ offsets.push(Number.isFinite(pct) ? Math.min(1, Math.max(0, pct / 100)) : undefined);
2415
+ }
2416
+ if (colors.length < 2)
2417
+ return undefined;
2418
+ if (offsets[0] === undefined)
2419
+ offsets[0] = 0;
2420
+ if (offsets[offsets.length - 1] === undefined)
2421
+ offsets[offsets.length - 1] = 1;
2422
+ let prev = 0;
2423
+ for (let i = 1; i < offsets.length; i++) {
2424
+ if (offsets[i] === undefined)
2425
+ continue;
2426
+ const gap = i - prev;
2427
+ for (let j = prev + 1; j < i; j++) {
2428
+ offsets[j] = offsets[prev] + ((offsets[i] - offsets[prev]) * (j - prev)) / gap;
2429
+ }
2430
+ prev = i;
2431
+ }
2432
+ return { angle, stops: colors.map((color, i) => ({ color, offset: offsets[i] })) };
2433
+ }
2434
+ // CSS gradient line for a w x h box: length |w*sin| + |h*cos|, centred on the box.
2435
+ function gradientLine(angleDeg, width, height) {
2436
+ const theta = (angleDeg * Math.PI) / 180;
2437
+ const length = Math.abs(width * Math.sin(theta)) + Math.abs(height * Math.cos(theta));
2438
+ const dx = (Math.sin(theta) * length) / 2;
2439
+ const dy = (-Math.cos(theta) * length) / 2;
2440
+ const cx = width / 2;
2441
+ const cy = height / 2;
2442
+ return { x1: cx - dx, y1: cy - dy, x2: cx + dx, y2: cy + dy };
2443
+ }
2444
+ // NAM-3538: solid stand-in when SVG is unavailable (VegaOS) — the last stop keeps a
2445
+ // "darken toward the bottom" scrim as a uniform darken rather than nothing.
2446
+ function pickOverlayFallbackColor(spec) {
2447
+ return spec.stops[spec.stops.length - 1]?.color;
2448
+ }
2338
2449
  function parseSize(value, scaleFactor = 1) {
2339
2450
  if (value === undefined || value === null)
2340
2451
  return undefined;
@@ -2794,6 +2905,15 @@ function valueFromSmartText(ctx, value, sku, block = null) {
2794
2905
  }
2795
2906
  return value;
2796
2907
  }
2908
+ // NAM-3538: the background container bypasses TemplateRenderer, so only its overlayColor
2909
+ // is run through conditionAttributes + smart text.
2910
+ function resolveOverlayColor(ctx, component) {
2911
+ if (!component)
2912
+ return undefined;
2913
+ const resolved = component.conditionAttributes ? withOverrides(ctx, component) : component;
2914
+ const value = valueFromSmartText(ctx, resolved.overlayColor);
2915
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
2916
+ }
2797
2917
  function resolveComponentSmartText(component, ctx) {
2798
2918
  const smartTextSku = componentSmartTextSku(component);
2799
2919
  const replacements = buildSmartTextReplacements(ctx.state, ctx.flow, smartTextSku);
@@ -3115,14 +3235,14 @@ const NamiContentContainer = ({ component, scaleFactor, onClose, }) => {
3115
3235
  }, [component.verticalAlignment, component.horizontalAlignment, flexDir]);
3116
3236
  const renderedChildren = require$$0.useMemo(() => {
3117
3237
  return component.components?.map((comp, i) => (jsxRuntimeExports.jsx(reactNative.View, { style: [
3118
- styles$6.fullWidth,
3238
+ styles$7.fullWidth,
3119
3239
  i === 0
3120
3240
  ? null
3121
3241
  : childSpacingStyle(i, { spacing: component.spacing, direction }, scaleFactor),
3122
3242
  ], children: jsxRuntimeExports.jsx(TemplateRenderer, { component: comp, scaleFactor: scaleFactor, onClose: onClose, parentDirection: direction }) }, comp.id ?? i)));
3123
3243
  }, [component.components, component.spacing, direction, scaleFactor, onClose]);
3124
3244
  const innerContentStyle = require$$0.useMemo(() => [
3125
- styles$6.inner,
3245
+ styles$7.inner,
3126
3246
  {
3127
3247
  flexDirection: flexDir,
3128
3248
  },
@@ -3130,11 +3250,11 @@ const NamiContentContainer = ({ component, scaleFactor, onClose, }) => {
3130
3250
  contentAlignment,
3131
3251
  ], [flexDir, inner, contentAlignment]);
3132
3252
  if (!scrollEnabled) {
3133
- return (jsxRuntimeExports.jsx(reactNative.View, { style: [styles$6.outer, outer], children: jsxRuntimeExports.jsx(reactNative.View, { style: [styles$6.scroll, innerContentStyle], children: renderedChildren }) }));
3253
+ return (jsxRuntimeExports.jsx(reactNative.View, { style: [styles$7.outer, outer], children: jsxRuntimeExports.jsx(reactNative.View, { style: [styles$7.scroll, innerContentStyle], children: renderedChildren }) }));
3134
3254
  }
3135
- return (jsxRuntimeExports.jsx(reactNative.View, { style: [styles$6.outer, outer], children: jsxRuntimeExports.jsx(reactNative.ScrollView, { style: styles$6.scroll, contentContainerStyle: innerContentStyle, showsVerticalScrollIndicator: false, scrollEnabled: true, children: renderedChildren }) }));
3255
+ return (jsxRuntimeExports.jsx(reactNative.View, { style: [styles$7.outer, outer], children: jsxRuntimeExports.jsx(reactNative.ScrollView, { style: styles$7.scroll, contentContainerStyle: innerContentStyle, showsVerticalScrollIndicator: false, scrollEnabled: true, children: renderedChildren }) }));
3136
3256
  };
3137
- const styles$6 = reactNative.StyleSheet.create({
3257
+ const styles$7 = reactNative.StyleSheet.create({
3138
3258
  outer: {
3139
3259
  flex: 1,
3140
3260
  minHeight: 0,
@@ -3152,10 +3272,76 @@ const styles$6 = reactNative.StyleSheet.create({
3152
3272
  },
3153
3273
  });
3154
3274
 
3275
+ // react-native-svg may be missing on some runtimes (e.g. VegaOS); fall back to a solid color.
3276
+ let SvgComponent = null;
3277
+ let DefsComponent = null;
3278
+ let LinearGradientComponent = null;
3279
+ let StopComponent = null;
3280
+ let RectComponent = null;
3281
+ try {
3282
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
3283
+ const svg = require('react-native-svg');
3284
+ SvgComponent = svg.default ?? svg.Svg ?? null;
3285
+ DefsComponent = svg.Defs ?? null;
3286
+ LinearGradientComponent = svg.LinearGradient ?? null;
3287
+ StopComponent = svg.Stop ?? null;
3288
+ RectComponent = svg.Rect ?? null;
3289
+ }
3290
+ catch {
3291
+ SvgComponent = null;
3292
+ }
3293
+ const RADIUS_KEYS = [
3294
+ 'borderRadius',
3295
+ 'borderTopLeftRadius',
3296
+ 'borderTopRightRadius',
3297
+ 'borderBottomLeftRadius',
3298
+ 'borderBottomRightRadius',
3299
+ ];
3300
+ // NAM-3538: scrim painted above the background container's children, below the page content.
3301
+ const BackgroundOverlay = ({ color, radiusStyle }) => {
3302
+ const [size, setSize] = require$$0.useState({ w: 0, h: 0 });
3303
+ const radii = {};
3304
+ RADIUS_KEYS.forEach((key) => {
3305
+ if (radiusStyle[key] !== undefined)
3306
+ radii[key] = radiusStyle[key];
3307
+ });
3308
+ const base = [styles$6.overlay, radii];
3309
+ const solid = parseColor(color);
3310
+ if (solid) {
3311
+ return jsxRuntimeExports.jsx(reactNative.View, { pointerEvents: 'none', style: [...base, { backgroundColor: solid }] });
3312
+ }
3313
+ const spec = parseLinearGradient(color);
3314
+ if (!spec)
3315
+ return null;
3316
+ const canRenderSvg = Boolean(SvgComponent && DefsComponent && LinearGradientComponent && StopComponent && RectComponent);
3317
+ if (!canRenderSvg) {
3318
+ return jsxRuntimeExports.jsx(reactNative.View, { pointerEvents: 'none', style: [...base, { backgroundColor: pickOverlayFallbackColor(spec) }] });
3319
+ }
3320
+ const Svg = SvgComponent;
3321
+ const Defs = DefsComponent;
3322
+ const Gradient = LinearGradientComponent;
3323
+ const Stop = StopComponent;
3324
+ const Rect = RectComponent;
3325
+ const onLayout = (e) => {
3326
+ const { width, height } = e.nativeEvent.layout;
3327
+ setSize((prev) => (prev.w === width && prev.h === height ? prev : { w: width, h: height }));
3328
+ };
3329
+ return (jsxRuntimeExports.jsx(reactNative.View, { pointerEvents: 'none', style: base, onLayout: onLayout, children: size.w > 0 && size.h > 0 ? (jsxRuntimeExports.jsxs(Svg, { width: size.w, height: size.h, children: [jsxRuntimeExports.jsx(Defs, { children: jsxRuntimeExports.jsx(Gradient, { id: 'namiOverlay', gradientUnits: 'userSpaceOnUse', ...gradientLine(spec.angle, size.w, size.h), children: spec.stops.map((stop, i) => (jsxRuntimeExports.jsx(Stop, { offset: stop.offset, stopColor: stop.color }, i))) }) }), jsxRuntimeExports.jsx(Rect, { width: size.w, height: size.h, fill: 'url(#namiOverlay)' })] })) : null }));
3330
+ };
3331
+ const styles$6 = reactNative.StyleSheet.create({
3332
+ overlay: {
3333
+ ...reactNative.StyleSheet.absoluteFillObject,
3334
+ overflow: 'hidden',
3335
+ },
3336
+ });
3337
+
3155
3338
  const NamiBackgroundContainer = ({ component, scaleFactor, onClose }) => {
3339
+ const ctx = usePaywallContext();
3340
+ const containerStyle = require$$0.useMemo(() => (component ? applyStyles(component, scaleFactor) : {}), [component, scaleFactor]);
3341
+ // NAM-3538: only overlayColor is resolved here; the container itself skips TemplateRenderer.
3342
+ const overlayColor = require$$0.useMemo(() => resolveOverlayColor(ctx, component), [ctx, component]);
3156
3343
  if (!component || component.hidden)
3157
3344
  return null;
3158
- const containerStyle = require$$0.useMemo(() => applyStyles(component, scaleFactor), [component, scaleFactor]);
3159
3345
  const fillImageUrl = resolveFillImageUrl(component.fillImage);
3160
3346
  const bgColor = parseColor(component.fillColor) ?? parseColor(component.fillColorFallback);
3161
3347
  // Only clip when the container is a filled "tile" with specific (non-all-corners)
@@ -3175,10 +3361,11 @@ const NamiBackgroundContainer = ({ component, scaleFactor, onClose }) => {
3175
3361
  };
3176
3362
  return (jsxRuntimeExports.jsx(TemplateRenderer, { component: spacedComponent, scaleFactor: scaleFactor, onClose: onClose, parentDirection: direction }, comp.id ?? i));
3177
3363
  }) }));
3364
+ const overlay = overlayColor ? jsxRuntimeExports.jsx(BackgroundOverlay, { color: overlayColor, radiusStyle: containerStyle }) : null;
3178
3365
  if (fillImageUrl) {
3179
- return (jsxRuntimeExports.jsx(reactNative.ImageBackground, { source: { uri: fillImageUrl }, resizeMode: 'cover', style: [containerStyle, styles$5.background, clipStyle], children: content }));
3366
+ return (jsxRuntimeExports.jsxs(reactNative.ImageBackground, { source: { uri: fillImageUrl }, resizeMode: 'cover', style: [containerStyle, styles$5.background, clipStyle], children: [content, overlay] }));
3180
3367
  }
3181
- return (jsxRuntimeExports.jsx(reactNative.View, { style: [containerStyle, bgColor ? { backgroundColor: bgColor } : null, styles$5.background, clipStyle], children: content }));
3368
+ return (jsxRuntimeExports.jsxs(reactNative.View, { style: [containerStyle, bgColor ? { backgroundColor: bgColor } : null, styles$5.background, clipStyle], children: [content, overlay] }));
3182
3369
  };
3183
3370
  function mergeRawMarginValue(existing, spacing) {
3184
3371
  if (existing == null)