w-funnel/src/lib/funnel/variants.ts
2025-09-26 02:47:16 +02:00

79 lines
2.1 KiB
TypeScript

import { matchesNavigationConditions } from "./navigation";
import type {
FunnelAnswers,
ScreenDefinition,
ScreenVariantDefinition,
} from "./types";
function cloneScreen<T>(screen: T): T {
if (typeof globalThis.structuredClone === "function") {
return globalThis.structuredClone(screen);
}
return JSON.parse(JSON.stringify(screen)) as T;
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function deepMerge<T>(target: T, source: Partial<T> | undefined): T {
if (!source) {
return target;
}
const result: T = Array.isArray(target)
? ([...target] as unknown as T)
: ({ ...(target as Record<string, unknown>) } as T);
for (const key of Object.keys(source) as (keyof T)[]) {
const sourceValue = source[key];
if (sourceValue === undefined) {
continue;
}
const targetValue = (result as Record<string, unknown>)[key as unknown as string];
if (isPlainObject(sourceValue)) {
const baseValue = isPlainObject(targetValue) ? targetValue : {};
(result as Record<string, unknown>)[key as unknown as string] = deepMerge(
baseValue,
sourceValue as Record<string, unknown>
) as unknown as T[keyof T];
continue;
}
(result as Record<string, unknown>)[key as unknown as string] = sourceValue as unknown as T[keyof T];
}
return result;
}
function applyScreenOverrides<T extends ScreenDefinition>(
screen: T,
overrides: ScreenVariantDefinition<T>["overrides"]
): T {
const cloned = cloneScreen(screen);
return deepMerge(cloned, overrides);
}
export function resolveScreenVariant<T extends ScreenDefinition>(
screen: T,
answers: FunnelAnswers
): T {
const variants = (screen as T & { variants?: ScreenVariantDefinition<T>[] }).variants;
if (!variants || variants.length === 0) {
return screen;
}
for (const variant of variants) {
if (matchesNavigationConditions(variant.conditions, answers)) {
return applyScreenOverrides(screen, variant.overrides);
}
}
return screen;
}