UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 6.5 kB
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../src/libs/collection/custom-fields/conditions/index.ts"],"sourcesContent":["/**\n * Field condition config and evaluator.\n *\n * This module must stay dependency free. The admin SPA bundles it directly\n * via the `@field-conditions` alias so the admin and the backend share the\n * same deterministic evaluation logic.\n */\n\nexport const fieldConditionOperators = [\n\t\"equals\",\n\t\"notEquals\",\n\t\"isEmpty\",\n\t\"isNotEmpty\",\n\t\"contains\",\n\t\"notContains\",\n] as const;\n\nexport type FieldConditionOperator = (typeof fieldConditionOperators)[number];\n\nexport const fieldConditionTranslationScopes = [\n\t\"same\",\n\t\"default\",\n\t\"any\",\n] as const;\n\nexport type FieldConditionTranslationScope =\n\t(typeof fieldConditionTranslationScopes)[number];\n\nexport type FieldConditionRuleValue = string | number | boolean | null;\n\nexport type FieldConditionRule = {\n\tfield: string;\n\toperator: FieldConditionOperator;\n\tvalue?: FieldConditionRuleValue;\n};\n\nexport type FieldConditionAction = \"show\" | \"hide\";\n\nexport type FieldConditionConfig = {\n\taction?: FieldConditionAction;\n\ttranslationScope?: FieldConditionTranslationScope;\n\t/** Outer array is OR'd, inner rule arrays are AND'd. */\n\tgroups: FieldConditionRule[][];\n};\n\nexport type FieldConditionTargetResolution =\n\t| {\n\t\t\tresolved: true;\n\t\t\tvalue: unknown;\n\t }\n\t| {\n\t\t\tresolved: true;\n\t\t\tmatch: \"any\";\n\t\t\tvalues: unknown[];\n\t }\n\t| {\n\t\t\tresolved: false;\n\t };\n\nexport type FieldConditionTargetResolver = (\n\tfieldKey: string,\n) => FieldConditionTargetResolution;\n\n/**\n * Compares two condition values. Booleans compare loosely against their\n * integer form as some adapters store checkbox values as 0/1.\n */\nconst conditionValuesEqual = (a: unknown, b: unknown): boolean => {\n\tif (a === null || a === undefined) return b === null || b === undefined;\n\tif (b === null || b === undefined) return false;\n\n\tif (typeof a === \"boolean\" || typeof b === \"boolean\") {\n\t\tconst left = toBooleanComparable(a);\n\t\tconst right = toBooleanComparable(b);\n\t\tif (left === undefined || right === undefined) return false;\n\t\treturn left === right;\n\t}\n\n\treturn a === b;\n};\n\nconst toBooleanComparable = (value: unknown): boolean | undefined => {\n\tif (typeof value === \"boolean\") return value;\n\tif (value === 1) return true;\n\tif (value === 0) return false;\n\treturn undefined;\n};\n\nconst isEmptyValue = (value: unknown): boolean => {\n\tif (value === undefined || value === null || value === \"\") return true;\n\tif (Array.isArray(value) && value.length === 0) return true;\n\treturn false;\n};\n\nconst containsValue = (\n\tvalue: unknown,\n\tsearch: FieldConditionRuleValue | undefined,\n): boolean => {\n\tif (Array.isArray(value)) {\n\t\treturn value.some((item) => conditionValuesEqual(item, search ?? null));\n\t}\n\tif (typeof value === \"string\") {\n\t\tif (typeof search === \"string\") {\n\t\t\treturn search.length > 0 && value.includes(search);\n\t\t}\n\t\tif (typeof search === \"number\" || typeof search === \"boolean\") {\n\t\t\treturn value.includes(String(search));\n\t\t}\n\t\treturn false;\n\t}\n\treturn false;\n};\n\n/**\n * Evaluates a single condition rule against a resolved target value.\n */\nexport const evaluateConditionRule = (\n\trule: FieldConditionRule,\n\tvalue: unknown,\n): boolean => {\n\tswitch (rule.operator) {\n\t\tcase \"equals\":\n\t\t\treturn conditionValuesEqual(value, rule.value ?? null);\n\t\tcase \"notEquals\":\n\t\t\treturn !conditionValuesEqual(value, rule.value ?? null);\n\t\tcase \"isEmpty\":\n\t\t\treturn isEmptyValue(value);\n\t\tcase \"isNotEmpty\":\n\t\t\treturn !isEmptyValue(value);\n\t\tcase \"contains\":\n\t\t\treturn containsValue(value, rule.value);\n\t\tcase \"notContains\":\n\t\t\treturn !containsValue(value, rule.value);\n\t\tdefault:\n\t\t\treturn false;\n\t}\n};\n\n/**\n * Evaluates a field condition and returns whether the field is visible.\n *\n * - Groups are OR'd, rules within a group are AND'd.\n * - Rules that reference an unresolvable target fail their group.\n * - Conditions without any non-empty rule group leave the field visible.\n */\nexport const evaluateFieldCondition = (\n\tcondition: FieldConditionConfig | undefined,\n\tresolveTarget: FieldConditionTargetResolver,\n): boolean => {\n\tif (!condition) return true;\n\n\tconst groups = (condition.groups ?? []).filter((rules) => rules.length > 0);\n\tif (groups.length === 0) return true;\n\n\tconst matched = groups.some((rules) =>\n\t\trules.every((rule) => {\n\t\t\tconst target = resolveTarget(rule.field);\n\t\t\tif (!target.resolved) return false;\n\t\t\tif (\"values\" in target) {\n\t\t\t\treturn target.values.some((value) =>\n\t\t\t\t\tevaluateConditionRule(rule, value),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn evaluateConditionRule(rule, target.value);\n\t\t}),\n\t);\n\n\treturn (condition.action ?? \"show\") === \"show\" ? matched : !matched;\n};\n"],"mappings":"AAQA,MAAa,EAA0B,CACtC,SACA,YACA,UACA,aACA,WACA,aACD,EAIa,EAAkC,CAC9C,OACA,UACA,KACD,EA4CM,GAAwB,EAAY,IAAwB,CACjE,GAAI,GAAM,KAAyB,OAAO,GAAM,KAChD,GAAI,GAAM,KAAyB,MAAO,GAE1C,GAAI,OAAO,GAAM,WAAa,OAAO,GAAM,UAAW,CACrD,IAAM,EAAO,EAAoB,CAAC,EAC5B,EAAQ,EAAoB,CAAC,EAEnC,OADI,IAAS,IAAA,IAAa,IAAU,IAAA,GAAkB,GAC/C,IAAS,CACjB,CAEA,OAAO,IAAM,CACd,EAEM,EAAuB,GAAwC,CACpE,GAAI,OAAO,GAAU,UAAW,OAAO,EACvC,GAAI,IAAU,EAAG,MAAO,GACxB,GAAI,IAAU,EAAG,MAAO,EAEzB,EAEM,EAAgB,GAErB,GADI,GAAiC,MAAQ,IAAU,IACnD,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAW,GAIxC,GACL,EACA,IAEI,MAAM,QAAQ,CAAK,EACf,EAAM,KAAM,GAAS,EAAqB,EAAM,GAAU,IAAI,CAAC,EAEnE,OAAO,GAAU,SAChB,OAAO,GAAW,SACd,EAAO,OAAS,GAAK,EAAM,SAAS,CAAM,EAE9C,OAAO,GAAW,UAAY,OAAO,GAAW,UAC5C,EAAM,SAAS,OAAO,CAAM,CAAC,EAE9B,GAED,GAMK,GACZ,EACA,IACa,CACb,OAAQ,EAAK,SAAb,CACC,IAAK,SACJ,OAAO,EAAqB,EAAO,EAAK,OAAS,IAAI,EACtD,IAAK,YACJ,MAAO,CAAC,EAAqB,EAAO,EAAK,OAAS,IAAI,EACvD,IAAK,UACJ,OAAO,EAAa,CAAK,EAC1B,IAAK,aACJ,MAAO,CAAC,EAAa,CAAK,EAC3B,IAAK,WACJ,OAAO,EAAc,EAAO,EAAK,KAAK,EACvC,IAAK,cACJ,MAAO,CAAC,EAAc,EAAO,EAAK,KAAK,EACxC,QACC,MAAO,EACT,CACD,EASa,GACZ,EACA,IACa,CACb,GAAI,CAAC,EAAW,MAAO,GAEvB,IAAM,GAAU,EAAU,QAAU,CAAC,EAAA,CAAG,OAAQ,GAAU,EAAM,OAAS,CAAC,EAC1E,GAAI,EAAO,SAAW,EAAG,MAAO,GAEhC,IAAM,EAAU,EAAO,KAAM,GAC5B,EAAM,MAAO,GAAS,CACrB,IAAM,EAAS,EAAc,EAAK,KAAK,EAOvC,OANK,EAAO,SACR,WAAY,EACR,EAAO,OAAO,KAAM,GAC1B,EAAsB,EAAM,CAAK,CAClC,EAEM,EAAsB,EAAM,EAAO,KAAK,EANlB,EAO9B,CAAC,CACF,EAEA,OAAQ,EAAU,QAAU,UAAY,OAAS,EAAU,CAAC,CAC7D"}