79 lines
2.1 KiB
TypeScript
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;
|
|
}
|