Skip to main content
cngx-src documentation

CNGX_FILTER_BUILTIN_OPERATOR_DEFS

Variableforms/filter-builder/config

projects/forms/filter-builder/filter-builder-operators.ts

Description#

The 14 builtin operator definitions: the 11 historical switch arms re-expressed as data, plus the opt-in between / in / notIn family. Default value of the CNGX_FILTER_BUILDER_CONFIG.operators slice and the fallback registry evaluateExpression resolves against when no options are passed - keeping the no-options evaluation path bit-identical to the historical closed switch.

The opt-in trio is deliberately absent from DEFAULT_OPERATORS: no field grows a picker entry its native editor cannot serve. Expose the keys per field via FilterFieldDef.operators (or per editor type via withDefaultOperators) together with a value editor that produces the matching array shape - [min, max] for between, a value list for in / notIn.

Type#

ReadonlyMap

Default value#

new Map([
    // Labels for the builtins live in the default i18n bundle
    // (DEFAULT_I18N.operators) - def.label is the tier for
    // consumer-registered keys only, so the string exists once.
    ['eq', { evaluate: (itemValue, exprValue) => Object.is(itemValue, exprValue) }],
    ['neq', { evaluate: (itemValue, exprValue) => !Object.is(itemValue, exprValue) }],
    ['isEmpty', { valueless: true, evaluate: (itemValue) => itemValue == null || itemValue === '' }],
    [
      'isNotEmpty',
      { valueless: true, evaluate: (itemValue) => itemValue != null && itemValue !== '' },
    ],
    ['contains', substringDef((a, b) => a.includes(b))],
    ['startsWith', substringDef((a, b) => a.startsWith(b))],
    ['endsWith', substringDef((a, b) => a.endsWith(b))],
    ['gt', { evaluate: (a, b) => compare(a, b) > 0 }],
    ['gte', { evaluate: (a, b) => compare(a, b) >= 0 }],
    ['lt', { evaluate: (a, b) => compare(a, b) < 0 }],
    ['lte', { evaluate: (a, b) => compare(a, b) <= 0 }],
    [
      'between',
      {
        // Value shape [min, max]. A non-array (or wrong-arity) value is a
        // wiring bug -> conservative false; a nullish bound means the range
        // editor is half-filled -> no-op true, mirroring the empty-value
        // guard that cannot see inside arrays.
        evaluate: (itemValue, exprValue) => {
          if (!Array.isArray(exprValue) || exprValue.length !== 2) {
            return false;
          }
          const [min, max] = exprValue as [unknown, unknown];
          if (min == null || max == null) {
            return true;
          }
          return compare(itemValue, min) >= 0 && compare(itemValue, max) <= 0;
        },
      },
    ],
    [
      'in',
      {
        // An empty list is an unfilled editor, not "match nothing" - the
        // no-op guard cannot see inside arrays, so the def honours the
        // unfilled-row contract itself (mirrors the between bound guard).
        evaluate: (itemValue, exprValue) => {
          if (!Array.isArray(exprValue)) {
            return false;
          }
          if (exprValue.length === 0) {
            return true;
          }
          return exprValue.some((v) => Object.is(v, itemValue));
        },
      },
    ],
    [
      'notIn',
      {
        evaluate: (itemValue, exprValue) => {
          if (!Array.isArray(exprValue)) {
            return false;
          }
          if (exprValue.length === 0) {
            return true;
          }
          return !exprValue.some((v) => Object.is(v, itemValue));
        },
      },
    ],
  ])