Build standalone CiviCRM check-in middleware
This commit is contained in:
80
lib/conditional.ts
Normal file
80
lib/conditional.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Visibility engine for form sections and fields.
|
||||
*
|
||||
* Evaluates a VisibilityRule tree against the current form state.
|
||||
* Form state is a plain object {fieldName: value}. Missing fields are
|
||||
* treated as undefined.
|
||||
*
|
||||
* Rule semantics:
|
||||
* - SimpleCondition: tests a single field with an operator
|
||||
* - {all: [...]}: AND
|
||||
* - {any: [...]}: OR
|
||||
*
|
||||
* Operator semantics:
|
||||
* - equals : field value === rule.value
|
||||
* - notEquals : field value !== rule.value
|
||||
* - in : rule.values includes the field value
|
||||
* - notIn : rule.values does not include the field value
|
||||
* - isEmpty : field is undefined / null / "" / [] / {}
|
||||
* - isNotEmpty : negation of isEmpty
|
||||
*
|
||||
* If a rule is malformed, evaluate returns false (field stays hidden) rather
|
||||
* than throwing, so a config typo can never break the page.
|
||||
*/
|
||||
|
||||
import type {
|
||||
VisibilityRule,
|
||||
SimpleCondition,
|
||||
AllCondition,
|
||||
AnyCondition,
|
||||
} from "@/types/form";
|
||||
|
||||
type FormState = Record<string, unknown>;
|
||||
|
||||
function isSimple(r: VisibilityRule): r is SimpleCondition {
|
||||
return typeof (r as SimpleCondition).field === "string";
|
||||
}
|
||||
|
||||
function isAll(r: VisibilityRule): r is AllCondition {
|
||||
return Array.isArray((r as AllCondition).all);
|
||||
}
|
||||
|
||||
function isAny(r: VisibilityRule): r is AnyCondition {
|
||||
return Array.isArray((r as AnyCondition).any);
|
||||
}
|
||||
|
||||
function isEmpty(v: unknown): boolean {
|
||||
if (v === undefined || v === null) return true;
|
||||
if (typeof v === "string") return v.length === 0;
|
||||
if (Array.isArray(v)) return v.length === 0;
|
||||
if (typeof v === "object") return Object.keys(v as object).length === 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
function evalSimple(c: SimpleCondition, state: FormState): boolean {
|
||||
const v = state[c.field];
|
||||
switch (c.op) {
|
||||
case "equals":
|
||||
return v === c.value;
|
||||
case "notEquals":
|
||||
return v !== c.value;
|
||||
case "in":
|
||||
return Array.isArray(c.values) && c.values.includes(v as string | number | boolean);
|
||||
case "notIn":
|
||||
return Array.isArray(c.values) && !c.values.includes(v as string | number | boolean);
|
||||
case "isEmpty":
|
||||
return isEmpty(v);
|
||||
case "isNotEmpty":
|
||||
return !isEmpty(v);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function evaluate(rule: VisibilityRule | undefined, state: FormState): boolean {
|
||||
if (!rule) return true;
|
||||
if (isSimple(rule)) return evalSimple(rule, state);
|
||||
if (isAll(rule)) return rule.all.every((r) => evaluate(r, state));
|
||||
if (isAny(rule)) return rule.any.some((r) => evaluate(r, state));
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user