@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 +195 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +195 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/components/containers/BackgroundOverlay.tsx +94 -0
- package/src/components/containers/NamiBackgroundContainer.tsx +13 -3
- package/src/utils/rendering.ts +9 -0
- package/src/utils/styles.ts +68 -5
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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.
|
|
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.
|
|
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)
|