UNPKG

@revenuecat/purchases-ui-js

Version:

Web components for Paywalls. Powered by RevenueCat

194 lines (193 loc) 7.48 kB
function getPaywallComponentChildNodes(node) { switch (node.type) { case "stack": { const children = [...node.components]; if (node.badge) { children.push(node.badge.stack); } return children; } case "button": case "purchase_button": case "redemption_button": return [node.stack]; case "carousel": return node.pages; case "countdown": { const children = [node.countdown_stack]; if (node.end_stack) { children.push(node.end_stack); } return children; } case "footer": return [node.stack]; case "input_multiple_choice": case "input_single_choice": case "input_option": return [node.stack]; case "tabs": return [node.control.stack, ...node.tabs]; case "tab": return [node.stack]; case "tab_control_button": return [node.stack]; case "timeline": return node.items.flatMap((item) => { const children = [item.icon, item.title]; if (item.description) { children.push(item.description); } return children; }); case "express_purchase_button": return [ node.wallet_unknown_stack, node.wallet_available_stack, node.wallet_unavailable_stack, ]; default: return []; } } function findFirstPackageMatching(root, predicate) { const stack = [root]; while (stack.length > 0) { const node = stack.pop(); if (node.type === "package" && predicate(node)) { return node; } const children = getPaywallComponentChildNodes(node); for (let i = children.length - 1; i >= 0; i--) { stack.push(children[i]); } } return undefined; } /** * Given an instance of PaywallData, returns the id of the first package marked as `is_selected_by_default` if any. * If none are marked, returns the first package encountered in traversal order (root stack, then sticky footer). * @param paywallData * @returns the id of the first package marked as `is_selected_by_default`, otherwise the first package id, or undefined */ export function findSelectedPackageId({ stack, sticky_footer, }) { const defaultPkg = findFirstPackageMatching(stack, (pkg) => pkg.is_selected_by_default); if (defaultPkg !== undefined) { return defaultPkg.package_id; } const firstPkg = findFirstPackageMatching(stack, () => true); if (firstPkg !== undefined) { return firstPkg.package_id; } if (sticky_footer != null) { const stickyDefault = findFirstPackageMatching(sticky_footer, (pkg) => pkg.is_selected_by_default); if (stickyDefault !== undefined) { return stickyDefault.package_id; } const stickyFirst = findFirstPackageMatching(sticky_footer, () => true); if (stickyFirst !== undefined) { return stickyFirst.package_id; } } return undefined; } export const getActiveStateProps = (selectedState, overrides) => { if (!selectedState) { return {}; } const override = overrides?.find((override) => override.conditions.find((condition) => condition.type === "selected")); return override?.properties ?? {}; }; export const getHoverStateProps = (hoverState, overrides) => { if (!hoverState) { return {}; } const override = overrides?.find((override) => override.conditions.find((condition) => condition.type === "hover")); return override?.properties ?? {}; }; export const getFocusStateProps = (focusState, overrides) => { if (!focusState) { return {}; } const override = overrides?.find((override) => override.conditions.find((condition) => condition.type === "focus")); return override?.properties ?? {}; }; export const getErrorStateProps = (errorState, overrides) => { if (!errorState) { return {}; } const override = overrides?.find((override) => override.conditions.find((condition) => condition.type === "error")); return override?.properties ?? {}; }; export const getIntroOfferStateProps = (hasIntroOffer, overrides) => { if (!hasIntroOffer) { return {}; } const override = overrides?.find((override) => override.conditions.find((condition) => condition.type === "intro_offer")); return override?.properties ?? {}; }; export const getPromoOfferStateProps = (hasPromoOffer, overrides) => { if (!hasPromoOffer) { return {}; } const override = overrides?.find((override) => override.conditions.find((condition) => condition.type === "promo_offer")); return override?.properties ?? {}; }; /** * Evaluates visibility for an element. * * A component is considered visible unless: * - baseVisible is explicitly false, or * - an override sets `visible: false` in its properties and all of its * conditions are satisfied by the current context. * * If baseVisible is undefined or null, the component defaults to visible. */ export const evaluateVisibilityConditions = (context, overrides, baseVisible) => { if (baseVisible === false) return false; const hidingOverride = overrides?.find((override) => { const properties = override.properties; if (properties.visible !== false) return false; return override.conditions.every((condition) => conditionMatches(condition, context)); }); return !hidingOverride; }; const conditionMatches = (condition, context) => { if (condition.type === "intro_offer_condition") { const hasIntroOffer = !!context.packageInfo?.hasIntroOffer || !!context.packageInfo?.hasTrial; if (condition.operator === "=") return hasIntroOffer === condition.value; if (condition.operator === "!=") return hasIntroOffer !== condition.value; } if (condition.type === "promo_offer_condition") { const hasPromoOffer = !!context.packageInfo?.hasPromoOffer; if (condition.operator === "=") return hasPromoOffer === condition.value; if (condition.operator === "!=") return hasPromoOffer !== condition.value; } if (condition.type === "selected_package_condition") { const selected = context.selectedPackageId ?? ""; if (condition.operator === "in") return condition.packages.includes(selected); if (condition.operator === "not in") return !condition.packages.includes(selected); } if (condition.type === "variable_condition") { const variableName = condition.variable.replace(/^\$?custom\./, ""); const currentValue = context.variables[variableName]; // API sends value as a plain scalar; type definition wraps it in CustomVariableValue const rawValue = condition.value; const conditionValue = String(typeof rawValue === "object" && rawValue !== null && "value" in rawValue ? rawValue.value : rawValue); if (condition.operator === "=") return currentValue === conditionValue; if (condition.operator === "!=") return currentValue !== conditionValue; } return false; };