UNPKG

@angular/forms

Version:

Angular - directives and services for creating forms

1,954 lines 57.1 kB
/**
 * @license Angular v22.1.4
 * (c) 2010-2026 Google LLC. https://angular.dev/
 * License: MIT
 */

import { untracked, ɵRuntimeError as _RuntimeError, computed, ɵisInParamsFunction as _isInParamsFunction, ɵsetInParamsFunction as _setInParamsFunction, runInInjectionContext, Injector, linkedSignal, signal, APP_ID, effect, InjectionToken, inject } from '@angular/core';
import { AbstractControl, FormGroup, FormArray } from '@angular/forms';
import { SIGNAL } from '@angular/core/primitives/signals';

const FIELD_TREE = /* @__PURE__ */Symbol('FIELD_TREE');

let boundPathDepth = 0;
function getBoundPathDepth() {
  return boundPathDepth;
}
function setBoundPathDepthForResolution(fn, depth) {
  return (...args) => {
    try {
      boundPathDepth = depth;
      return fn(...args);
    } finally {
      boundPathDepth = 0;
    }
  };
}

function shortCircuitFalse(value) {
  return !value;
}
function shortCircuitTrue(value) {
  return value;
}
function getInjectorFromOptions(options) {
  if (options.kind === 'root') {
    return options.fieldManager.injector;
  }
  return options.parent.structure.root.structure.injector;
}

function isArray(value) {
  return Array.isArray(value);
}
function isObject(value) {
  return (typeof value === 'object' || typeof value === 'function') && value != null;
}

const DYNAMIC = Symbol();
const IGNORED = Symbol();
class AbstractLogic {
  predicates;
  fns = [];
  constructor(predicates) {
    this.predicates = predicates;
  }
  push(logicFn) {
    this.fns.push(wrapWithPredicates(this.predicates, logicFn));
  }
  mergeIn(other) {
    const fns = this.predicates ? other.fns.map(fn => wrapWithPredicates(this.predicates, fn)) : other.fns;
    this.fns.push(...fns);
  }
  hasRules() {
    return this.fns.length > 0;
  }
}
class BooleanOrLogic extends AbstractLogic {
  get defaultValue() {
    return false;
  }
  compute(arg) {
    return this.fns.some(f => {
      const result = f(arg);
      return result && result !== IGNORED;
    });
  }
}
class ArrayMergeIgnoreLogic extends AbstractLogic {
  ignore;
  static ignoreNull(predicates) {
    return new ArrayMergeIgnoreLogic(predicates, e => e === null);
  }
  constructor(predicates, ignore) {
    super(predicates);
    this.ignore = ignore;
  }
  get defaultValue() {
    return [];
  }
  compute(arg) {
    return this.fns.reduce((prev, f) => {
      const value = f(arg);
      if (value === undefined || value === IGNORED) {
        return prev;
      } else if (isArray(value)) {
        return [...prev, ...(this.ignore ? value.filter(e => !this.ignore(e)) : value)];
      } else {
        if (this.ignore && this.ignore(value)) {
          return prev;
        }
        return [...prev, value];
      }
    }, []);
  }
}
class ArrayMergeLogic extends ArrayMergeIgnoreLogic {
  constructor(predicates) {
    super(predicates, undefined);
  }
}
class MetadataMergeLogic extends AbstractLogic {
  key;
  get defaultValue() {
    return this.key.reducer.getInitial();
  }
  constructor(predicates, key) {
    super(predicates);
    this.key = key;
  }
  compute(ctx) {
    if (this.fns.length === 0) {
      return this.key.reducer.getInitial();
    }
    let acc = this.key.reducer.getInitial();
    for (let i = 0; i < this.fns.length; i++) {
      const item = this.fns[i](ctx);
      if (item !== IGNORED) {
        acc = this.key.reducer.reduce(acc, item);
      }
    }
    return acc;
  }
}
function wrapWithPredicates(predicates, logicFn) {
  if (predicates.length === 0) {
    return logicFn;
  }
  return arg => {
    for (const predicate of predicates) {
      let predicateField = arg.stateOf(predicate.path);
      const depthDiff = untracked(predicateField.structure.pathKeys).length - predicate.depth;
      for (let i = 0; i < depthDiff; i++) {
        predicateField = predicateField.structure.parent;
      }
      if (!predicate.fn(predicateField.context)) {
        return IGNORED;
      }
    }
    return logicFn(arg);
  };
}
class LogicContainer {
  predicates;
  hidden;
  disabledReasons;
  readonly;
  syncErrors;
  syncTreeErrors;
  asyncErrors;
  metadata = new Map();
  constructor(predicates) {
    this.predicates = predicates;
    this.hidden = new BooleanOrLogic(predicates);
    this.disabledReasons = new ArrayMergeLogic(predicates);
    this.readonly = new BooleanOrLogic(predicates);
    this.syncErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
    this.syncTreeErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
    this.asyncErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
  }
  hasAnyLogic() {
    return this.hidden.hasRules() || this.disabledReasons.hasRules() || this.readonly.hasRules() || this.syncErrors.hasRules() || this.syncTreeErrors.hasRules() || this.asyncErrors.hasRules() || this.metadata.size > 0;
  }
  hasMetadata(key) {
    return this.metadata.has(key);
  }
  hasMetadataKeys() {
    return this.metadata.size > 0;
  }
  getMetadataKeys() {
    return this.metadata.keys();
  }
  getMetadata(key) {
    if (!this.metadata.has(key)) {
      this.metadata.set(key, new MetadataMergeLogic(this.predicates, key));
    }
    return this.metadata.get(key);
  }
  mergeIn(other) {
    this.hidden.mergeIn(other.hidden);
    this.disabledReasons.mergeIn(other.disabledReasons);
    this.readonly.mergeIn(other.readonly);
    this.syncErrors.mergeIn(other.syncErrors);
    this.syncTreeErrors.mergeIn(other.syncTreeErrors);
    this.asyncErrors.mergeIn(other.asyncErrors);
    for (const key of other.getMetadataKeys()) {
      const metadataLogic = other.metadata.get(key);
      this.getMetadata(key).mergeIn(metadataLogic);
    }
  }
}

class AbstractLogicNodeBuilder {
  depth;
  constructor(depth) {
    this.depth = depth;
  }
  build() {
    return new LeafLogicNode(this, [], 0);
  }
}
class LogicNodeBuilder extends AbstractLogicNodeBuilder {
  constructor(depth) {
    super(depth);
  }
  current;
  all = [];
  addHiddenRule(logic) {
    this.getCurrent().addHiddenRule(logic);
  }
  addDisabledReasonRule(logic) {
    this.getCurrent().addDisabledReasonRule(logic);
  }
  addReadonlyRule(logic) {
    this.getCurrent().addReadonlyRule(logic);
  }
  addSyncErrorRule(logic) {
    this.getCurrent().addSyncErrorRule(logic);
  }
  addSyncTreeErrorRule(logic) {
    this.getCurrent().addSyncTreeErrorRule(logic);
  }
  addAsyncErrorRule(logic) {
    this.getCurrent().addAsyncErrorRule(logic);
  }
  addMetadataRule(key, logic) {
    this.getCurrent().addMetadataRule(key, logic);
  }
  getChild(key) {
    if (key === DYNAMIC) {
      const children = this.getCurrent().children;
      if (children.size > (children.has(DYNAMIC) ? 1 : 0)) {
        this.current = undefined;
      }
    }
    return this.getCurrent().getChild(key);
  }
  hasLogic(builder) {
    if (this === builder) {
      return true;
    }
    return this.all.some(({
      builder: subBuilder
    }) => subBuilder.hasLogic(builder));
  }
  hasRules() {
    return this.all.length > 0;
  }
  anyChildHasLogic() {
    return this.all.some(({
      builder
    }) => builder.anyChildHasLogic());
  }
  mergeIn(other, predicate) {
    if (predicate) {
      this.all.push({
        builder: other,
        predicate: {
          fn: setBoundPathDepthForResolution(predicate.fn, this.depth),
          path: predicate.path
        }
      });
    } else {
      this.all.push({
        builder: other
      });
    }
    this.current = undefined;
  }
  getCurrent() {
    if (this.current === undefined) {
      this.current = new NonMergeableLogicNodeBuilder(this.depth);
      this.all.push({
        builder: this.current
      });
    }
    return this.current;
  }
  static newRoot() {
    return new LogicNodeBuilder(0);
  }
}
class NonMergeableLogicNodeBuilder extends AbstractLogicNodeBuilder {
  logic = new LogicContainer([]);
  children = new Map();
  constructor(depth) {
    super(depth);
  }
  addHiddenRule(logic) {
    this.logic.hidden.push(setBoundPathDepthForResolution(logic, this.depth));
  }
  addDisabledReasonRule(logic) {
    this.logic.disabledReasons.push(setBoundPathDepthForResolution(logic, this.depth));
  }
  addReadonlyRule(logic) {
    this.logic.readonly.push(setBoundPathDepthForResolution(logic, this.depth));
  }
  addSyncErrorRule(logic) {
    this.logic.syncErrors.push(setBoundPathDepthForResolution(logic, this.depth));
  }
  addSyncTreeErrorRule(logic) {
    this.logic.syncTreeErrors.push(setBoundPathDepthForResolution(logic, this.depth));
  }
  addAsyncErrorRule(logic) {
    this.logic.asyncErrors.push(setBoundPathDepthForResolution(logic, this.depth));
  }
  addMetadataRule(key, logic) {
    this.logic.getMetadata(key).push(setBoundPathDepthForResolution(logic, this.depth));
  }
  getChild(key) {
    if (!this.children.has(key)) {
      this.children.set(key, new LogicNodeBuilder(this.depth + 1));
    }
    return this.children.get(key);
  }
  hasLogic(builder) {
    return this === builder;
  }
  hasRules() {
    return this.logic.hasAnyLogic() || this.children.size > 0;
  }
  anyChildHasLogic() {
    for (const child of this.children.values()) {
      if (child.hasRules()) {
        return true;
      }
    }
    return false;
  }
}
class LeafLogicNode {
  builder;
  predicates;
  depth;
  logic;
  constructor(builder, predicates, depth) {
    this.builder = builder;
    this.predicates = predicates;
    this.depth = depth;
    this.logic = builder ? createLogic(builder, predicates, depth) : new LogicContainer([]);
  }
  getChild(key) {
    const childBuilders = this.builder ? getAllChildBuilders(this.builder, key) : [];
    if (childBuilders.length === 0) {
      return new LeafLogicNode(undefined, [], this.depth + 1);
    } else if (childBuilders.length === 1) {
      const {
        builder,
        predicates
      } = childBuilders[0];
      return new LeafLogicNode(builder, [...this.predicates, ...predicates.map(p => bindLevel(p, this.depth))], this.depth + 1);
    } else {
      const builtNodes = childBuilders.map(({
        builder,
        predicates
      }) => new LeafLogicNode(builder, [...this.predicates, ...predicates.map(p => bindLevel(p, this.depth))], this.depth + 1));
      return new CompositeLogicNode(builtNodes);
    }
  }
  hasLogic(builder) {
    if (!this.builder) {
      return false;
    }
    return this.builder.hasLogic(builder);
  }
  hasRules() {
    return this.builder ? this.builder.hasRules() : false;
  }
  anyChildHasLogic() {
    return this.builder ? this.builder.anyChildHasLogic() : false;
  }
}
class CompositeLogicNode {
  all;
  logic;
  constructor(all) {
    this.all = all;
    this.logic = new LogicContainer([]);
    for (const node of all) {
      this.logic.mergeIn(node.logic);
    }
  }
  getChild(key) {
    return new CompositeLogicNode(this.all.flatMap(child => child.getChild(key)));
  }
  hasLogic(builder) {
    return this.all.some(node => node.hasLogic(builder));
  }
  hasRules() {
    return this.all.some(node => node.hasRules());
  }
  anyChildHasLogic() {
    return this.all.some(child => child.anyChildHasLogic());
  }
}
function getAllChildBuilders(builder, key) {
  if (builder instanceof LogicNodeBuilder) {
    return builder.all.flatMap(({
      builder,
      predicate
    }) => {
      const children = getAllChildBuilders(builder, key);
      if (predicate) {
        return children.map(({
          builder,
          predicates
        }) => ({
          builder,
          predicates: [...predicates, predicate]
        }));
      }
      return children;
    });
  } else if (builder instanceof NonMergeableLogicNodeBuilder) {
    return [...(key !== DYNAMIC && builder.children.has(DYNAMIC) ? [{
      builder: builder.getChild(DYNAMIC),
      predicates: []
    }] : []), ...(builder.children.has(key) ? [{
      builder: builder.getChild(key),
      predicates: []
    }] : [])];
  } else {
    throw new _RuntimeError(1909, ngDevMode && 'Unknown LogicNodeBuilder type');
  }
}
function createLogic(builder, predicates, depth) {
  const logic = new LogicContainer(predicates);
  if (builder instanceof LogicNodeBuilder) {
    const builtNodes = builder.all.map(({
      builder,
      predicate
    }) => new LeafLogicNode(builder, predicate ? [...predicates, bindLevel(predicate, depth)] : predicates, depth));
    for (const node of builtNodes) {
      logic.mergeIn(node.logic);
    }
  } else if (builder instanceof NonMergeableLogicNodeBuilder) {
    logic.mergeIn(builder.logic);
  } else {
    throw new _RuntimeError(1909, ngDevMode && 'Unknown LogicNodeBuilder type');
  }
  return logic;
}
function bindLevel(predicate, depth) {
  return {
    ...predicate,
    depth: depth
  };
}

const PATH = Symbol('PATH');
class FieldPathNode {
  keys;
  parent;
  keyInParent;
  root;
  children = new Map();
  fieldPathProxy = new Proxy(this, FIELD_PATH_PROXY_HANDLER);
  logicBuilder;
  constructor(keys, root, parent, keyInParent) {
    this.keys = keys;
    this.parent = parent;
    this.keyInParent = keyInParent;
    this.root = root ?? this;
    if (!parent) {
      this.logicBuilder = LogicNodeBuilder.newRoot();
    }
  }
  get builder() {
    if (this.logicBuilder) {
      return this.logicBuilder;
    }
    return this.parent.builder.getChild(this.keyInParent);
  }
  getChild(key) {
    if (!this.children.has(key)) {
      this.children.set(key, new FieldPathNode([...this.keys, key], this.root, this, key));
    }
    return this.children.get(key);
  }
  mergeIn(other, predicate) {
    const path = other.compile();
    this.builder.mergeIn(path.builder, predicate);
  }
  static unwrapFieldPath(formPath) {
    return formPath[PATH];
  }
  static newRoot() {
    return new FieldPathNode([], undefined, undefined, undefined);
  }
}
const FIELD_PATH_PROXY_HANDLER = {
  get(node, property) {
    if (property === PATH) {
      return node;
    }
    return node.getChild(property).fieldPathProxy;
  }
};

let currentCompilingNode = undefined;
const compiledSchemas = new Map();
class SchemaImpl {
  schemaFn;
  constructor(schemaFn) {
    this.schemaFn = schemaFn;
  }
  compile() {
    if (compiledSchemas.has(this)) {
      return compiledSchemas.get(this);
    }
    const path = FieldPathNode.newRoot();
    compiledSchemas.set(this, path);
    let prevCompilingNode = currentCompilingNode;
    try {
      currentCompilingNode = path;
      this.schemaFn(path.fieldPathProxy);
    } finally {
      currentCompilingNode = prevCompilingNode;
    }
    return path;
  }
  static create(schema) {
    if (schema instanceof SchemaImpl) {
      return schema;
    }
    return new SchemaImpl(schema);
  }
  static rootCompile(schema) {
    try {
      compiledSchemas.clear();
      if (schema === undefined) {
        return FieldPathNode.newRoot();
      }
      if (schema instanceof SchemaImpl) {
        return schema.compile();
      }
      return new SchemaImpl(schema).compile();
    } finally {
      compiledSchemas.clear();
    }
  }
}
function isSchemaOrSchemaFn(value) {
  return value instanceof SchemaImpl || typeof value === 'function';
}
function assertPathIsCurrent(path) {
  if (currentCompilingNode !== FieldPathNode.unwrapFieldPath(path).root) {
    throw new _RuntimeError(1908, ngDevMode && `A FieldPath can only be used directly within the Schema that owns it, **not** outside of it or within a sub-schema.`);
  }
}

function metadata(path, key, logic) {
  assertPathIsCurrent(path);
  const pathNode = FieldPathNode.unwrapFieldPath(path);
  pathNode.builder.addMetadataRule(key, logic);
  return key;
}
const MetadataReducer = {
  list() {
    return {
      reduce: (acc, item) => item === undefined ? acc : [...acc, item],
      getInitial: () => []
    };
  },
  min() {
    return {
      reduce: (acc, item) => {
        if (acc === undefined || item === undefined) {
          return acc ?? item;
        }
        return item < acc ? item : acc;
      },
      getInitial: () => undefined
    };
  },
  max() {
    return {
      reduce: (acc, item) => {
        if (acc === undefined || item === undefined) {
          return acc ?? item;
        }
        return item > acc ? item : acc;
      },
      getInitial: () => undefined
    };
  },
  or() {
    return {
      reduce: (prev, next) => prev || next,
      getInitial: () => false
    };
  },
  and() {
    return {
      reduce: (prev, next) => prev && next,
      getInitial: () => true
    };
  },
  override
};
function override(getInitial) {
  return {
    reduce: (_, item) => item,
    getInitial: () => getInitial?.()
  };
}
const IS_ASYNC_VALIDATION_RESOURCE = Symbol('IS_ASYNC_VALIDATION_RESOURCE');
class MetadataKey {
  reducer;
  create;
  brand;
  [IS_ASYNC_VALIDATION_RESOURCE];
  constructor(reducer, create) {
    this.reducer = reducer;
    this.create = create;
  }
}
function createMetadataKey(reducer) {
  return new MetadataKey(reducer ?? MetadataReducer.override());
}
function createManagedMetadataKey(create, reducer) {
  return new MetadataKey(reducer ?? MetadataReducer.override(), create);
}
function createLimitSelectionKey() {
  return createMetadataKey();
}
const REQUIRED = createMetadataKey(MetadataReducer.or());
const MIN = createLimitSelectionKey();
const MIN_DATE = createMetadataKey(MetadataReducer.max());
const MIN_NUMBER = createMetadataKey(MetadataReducer.max());
const MAX = createLimitSelectionKey();
const MAX_DATE = createMetadataKey(MetadataReducer.min());
const MAX_NUMBER = createMetadataKey(MetadataReducer.min());
const MIN_LENGTH = createMetadataKey(MetadataReducer.max());
const MAX_LENGTH = createMetadataKey(MetadataReducer.min());
const PATTERN = createMetadataKey(MetadataReducer.list());

function shallowArrayEquals(a, b) {
  if (a === b) return true;
  if (!a || !b) return false;
  if (a.length !== b.length) return false;
  for (let i = 0; i < a.length; i++) {
    if (!Object.is(a[i], b[i])) return false;
  }
  return true;
}

function calculateValidationSelfStatus(state) {
  if (state.errors().length > 0) {
    return 'invalid';
  }
  if (state.pending()) {
    return 'unknown';
  }
  return 'valid';
}
class FieldValidationState {
  node;
  constructor(node) {
    this.node = node;
  }
  rawSyncTreeErrors = computed(() => {
    if (this.shouldSkipValidation()) {
      return [];
    }
    return [...this.node.logicNode.logic.syncTreeErrors.compute(this.node.context), ...(this.node.structure.parent?.validationState.rawSyncTreeErrors() ?? [])];
  }, {
    ...(ngDevMode ? {
      debugName: "rawSyncTreeErrors"
    } : {}),
    equal: shallowArrayEquals
  });
  syncErrors = computed(() => {
    if (this.shouldSkipValidation()) {
      return [];
    }
    return [...this.node.logicNode.logic.syncErrors.compute(this.node.context), ...this.syncTreeErrors(), ...normalizeErrors(this.node.submitState.submissionErrors())];
  }, {
    ...(ngDevMode ? {
      debugName: "syncErrors"
    } : {}),
    equal: shallowArrayEquals
  });
  syncValid = computed(() => {
    if (this.shouldSkipValidation()) {
      return true;
    }
    return this.node.structure.reduceChildren(this.syncErrors().length === 0, (child, value) => value && child.validationState.syncValid(), shortCircuitFalse);
  }, ...(ngDevMode ? [{
    debugName: "syncValid"
  }] : []));
  syncTreeErrors = computed(() => this.rawSyncTreeErrors().filter(err => err.fieldTree === this.node.fieldTree), {
    ...(ngDevMode ? {
      debugName: "syncTreeErrors"
    } : {}),
    equal: shallowArrayEquals
  });
  rawAsyncErrors = computed(() => {
    if (this.shouldSkipValidation()) {
      return [];
    }
    return [...this.node.logicNode.logic.asyncErrors.compute(this.node.context), ...(this.node.structure.parent?.validationState.rawAsyncErrors() ?? [])];
  }, {
    ...(ngDevMode ? {
      debugName: "rawAsyncErrors"
    } : {}),
    equal: shallowArrayEquals
  });
  asyncErrors = computed(() => {
    if (this.shouldSkipValidation()) {
      return [];
    }
    return this.rawAsyncErrors().filter(err => err === 'pending' || err.fieldTree === this.node.fieldTree);
  }, {
    ...(ngDevMode ? {
      debugName: "asyncErrors"
    } : {}),
    equal: shallowArrayEquals
  });
  parseErrors = computed(() => this.node.formFieldBindings().flatMap(field => field.parseErrors()), {
    ...(ngDevMode ? {
      debugName: "parseErrors"
    } : {}),
    equal: shallowArrayEquals
  });
  errors = computed(() => [...this.parseErrors(), ...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], {
    ...(ngDevMode ? {
      debugName: "errors"
    } : {}),
    equal: shallowArrayEquals
  });
  errorSummary = computed(() => {
    const errors = this.node.structure.reduceChildren(this.errors(), (child, result) => [...result, ...child.errorSummary()]);
    if (typeof ngServerMode === 'undefined' || !ngServerMode) {
      untracked(() => errors.sort(compareErrorPosition));
    }
    return errors;
  }, {
    ...(ngDevMode ? {
      debugName: "errorSummary"
    } : {}),
    equal: shallowArrayEquals
  });
  pending = computed(() => this.node.structure.reduceChildren(this.asyncErrors().includes('pending'), (child, value) => value || child.validationState.pending()), ...(ngDevMode ? [{
    debugName: "pending"
  }] : []));
  status = computed(() => {
    if (this.shouldSkipValidation()) {
      return 'valid';
    }
    let ownStatus = calculateValidationSelfStatus(this);
    return this.node.structure.reduceChildren(ownStatus, (child, value) => {
      if (value === 'invalid' || child.validationState.status() === 'invalid') {
        return 'invalid';
      } else if (value === 'unknown' || child.validationState.status() === 'unknown') {
        return 'unknown';
      }
      return 'valid';
    }, v => v === 'invalid');
  }, ...(ngDevMode ? [{
    debugName: "status"
  }] : []));
  valid = computed(() => this.status() === 'valid', ...(ngDevMode ? [{
    debugName: "valid"
  }] : []));
  invalid = computed(() => this.status() === 'invalid', ...(ngDevMode ? [{
    debugName: "invalid"
  }] : []));
  shouldSkipValidation = computed(() => this.node.hidden() || this.node.disabled() || this.node.readonly() || this.node.structure.isOrphaned(), ...(ngDevMode ? [{
    debugName: "shouldSkipValidation"
  }] : []));
}
function normalizeErrors(error) {
  if (error === undefined) {
    return [];
  }
  if (isArray(error)) {
    return error;
  }
  return [error];
}
function addDefaultField(errors, fieldTree) {
  if (isArray(errors)) {
    for (const error of errors) {
      error.fieldTree ??= fieldTree;
    }
  } else if (errors) {
    errors.fieldTree ??= fieldTree;
  }
  return errors;
}
function getFirstBoundElement(error) {
  if (error.formField) return error.formField.element;
  return error.fieldTree().formFieldBindings().reduce((el, binding) => {
    if (!el || !binding.element) return el ?? binding.element;
    return el.compareDocumentPosition(binding.element) & Node.DOCUMENT_POSITION_PRECEDING ? binding.element : el;
  }, undefined);
}
function compareErrorPosition(a, b) {
  const aEl = getFirstBoundElement(a);
  const bEl = getFirstBoundElement(b);
  if (aEl === bEl) return 0;
  if (aEl === undefined || bEl === undefined) return aEl === undefined ? 1 : -1;
  return aEl.compareDocumentPosition(bEl) & Node.DOCUMENT_POSITION_PRECEDING ? 1 : -1;
}

const DEBOUNCER = createMetadataKey();

class FieldNodeContext {
  node;
  cache = new WeakMap();
  constructor(node) {
    this.node = node;
    this.fieldTreeOf = this.fieldTreeOf.bind(this);
    this.stateOf = this.stateOf.bind(this);
  }
  resolve(target) {
    if (!this.cache.has(target)) {
      const resolver = computed(() => {
        const targetPathNode = FieldPathNode.unwrapFieldPath(target);
        let field = this.node;
        let stepsRemaining = getBoundPathDepth();
        while (stepsRemaining > 0 || !field.structure.logic.hasLogic(targetPathNode.root.builder)) {
          stepsRemaining--;
          field = field.structure.parent;
          if (field === undefined) {
            throw new _RuntimeError(1900, ngDevMode && 'Path is not part of this field tree.');
          }
        }
        for (let key of targetPathNode.keys) {
          field = field.structure.getChild(key);
          if (field === undefined) {
            throw new _RuntimeError(1901, ngDevMode && `Cannot resolve path .${targetPathNode.keys.join('.')} relative to field ${['<root>', ...this.node.structure.pathKeys()].join('.')}.`);
          }
        }
        return field.fieldTree;
      }, ...(ngDevMode ? [{
        debugName: "resolver"
      }] : []));
      this.cache.set(target, resolver);
    }
    return this.cache.get(target)();
  }
  get fieldTree() {
    return this.node.fieldProxy;
  }
  get state() {
    return this.node;
  }
  get value() {
    return this.node.structure.value;
  }
  get key() {
    return this.node.structure.keyInParent;
  }
  get pathKeys() {
    return this.node.structure.pathKeys;
  }
  index = computed(() => {
    const key = this.key();
    if (!isArray(untracked(this.node.structure.parent.value))) {
      throw new _RuntimeError(1906, ngDevMode && 'Cannot access index, parent field is not an array.');
    }
    return Number(key);
  }, ...(ngDevMode ? [{
    debugName: "index"
  }] : []));
  fieldTreeOf(p) {
    return this.resolve(p);
  }
  stateOf(p) {
    return this.resolve(p)();
  }
  valueOf = p => {
    const result = this.resolve(p)().value();
    if (result instanceof AbstractControl) {
      throw new _RuntimeError(1907, ngDevMode && `Tried to read an 'AbstractControl' value from a 'form()'. Did you mean to use 'compatForm()' instead?`);
    }
    return result;
  };
}

class FieldMetadataState {
  node;
  metadata = new Map();
  constructor(node) {
    this.node = node;
  }
  runMetadataCreateLifecycle() {
    if (!this.node.logicNode.logic.hasMetadataKeys()) {
      return;
    }
    const wasInParams = _isInParamsFunction();
    if (wasInParams) _setInParamsFunction(false);
    try {
      untracked(() => runInInjectionContext(this.node.structure.injector, () => {
        for (const key of this.node.logicNode.logic.getMetadataKeys()) {
          if (key.create) {
            const logic = this.node.logicNode.logic.getMetadata(key);
            const result = key.create(this.node, computed(() => logic.compute(this.node.context)));
            this.metadata.set(key, result);
          }
        }
      }));
    } finally {
      if (wasInParams) _setInParamsFunction(true);
    }
  }
  get(key) {
    if (this.has(key)) {
      if (!this.metadata.has(key)) {
        if (key.create) {
          throw new _RuntimeError(1912, ngDevMode && 'Managed metadata cannot be created lazily');
        }
        const logic = this.node.logicNode.logic.getMetadata(key);
        this.metadata.set(key, computed(() => logic.compute(this.node.context)));
      }
    }
    return this.metadata.get(key);
  }
  has(key) {
    return this.node.logicNode.logic.hasMetadata(key);
  }
}

const FIELD_PROXY_HANDLER = {
  get(getTgt, property, receiver) {
    if (property === FIELD_TREE) {
      return true;
    }
    const tgt = getTgt();
    const child = tgt.structure.getChild(property);
    if (child !== undefined) {
      return child.fieldTree;
    }
    const value = untracked(tgt.value);
    if (isArray(value)) {
      if (property === 'length') {
        return tgt.value().length;
      }
      if (property === Symbol.iterator) {
        return () => {
          tgt.value();
          return Array.prototype[Symbol.iterator].apply(tgt.fieldTree);
        };
      }
    }
    if (isObject(value)) {
      if (property === Symbol.iterator) {
        return function* () {
          for (const key in receiver) {
            yield [key, receiver[key]];
          }
        };
      }
    }
    return undefined;
  },
  getOwnPropertyDescriptor(getTgt, prop) {
    const value = untracked(getTgt().value);
    const desc = Reflect.getOwnPropertyDescriptor(value, prop);
    if (desc && !desc.configurable) {
      desc.configurable = true;
    }
    return desc;
  },
  ownKeys(getTgt) {
    const value = untracked(getTgt().value);
    return typeof value === 'object' && value !== null ? Reflect.ownKeys(value) : [];
  }
};

function deepSignal(source, prop) {
  const read = computed(() => source()[prop()]);
  read[SIGNAL] = source[SIGNAL];
  read.set = value => {
    if (Object.is(untracked(read), value)) {
      return;
    }
    source.update(current => valueForWrite(current, value, prop()));
  };
  read.update = fn => {
    read.set(fn(untracked(read)));
  };
  read.asReadonly = () => read;
  return read;
}
function valueForWrite(sourceValue, newPropValue, prop) {
  if (isArray(sourceValue)) {
    const newValue = [...sourceValue];
    newValue[prop] = newPropValue;
    return newValue;
  } else {
    return {
      ...sourceValue,
      [prop]: newPropValue
    };
  }
}

const ORPHAN_TOKEN = Symbol(typeof ngDevMode !== 'undefined' && ngDevMode ? 'ORPHAN_TOKEN' : '');
const FALSE_SIGNAL = computed(() => false, ...(ngDevMode ? [{
  debugName: "FALSE_SIGNAL"
}] : []));
class FieldNodeStructure {
  logic;
  node;
  createChildNode;
  identitySymbol = Symbol();
  _injector = undefined;
  _anyChildHasLogic;
  get injector() {
    this._injector ??= Injector.create({
      providers: [],
      parent: this.fieldManager.injector
    });
    return this._injector;
  }
  constructor(logic, node, createChildNode) {
    this.logic = logic;
    this.node = node;
    this.createChildNode = createChildNode;
  }
  children() {
    this.ensureChildrenMap();
    const map = this.childrenMap();
    if (map === undefined) {
      return [];
    }
    return Array.from(map.byPropertyKey.values()).map(child => untracked(child.reader));
  }
  materializedChildren() {
    const map = this.childrenMap();
    if (map === undefined) {
      return [];
    }
    return Array.from(map.byPropertyKey.values()).map(child => child.node);
  }
  _areChildrenMaterialized() {
    return untracked(this.childrenMap) !== undefined;
  }
  ensureChildrenMap() {
    if (this._areChildrenMaterialized()) {
      return;
    }
    untracked(() => {
      this.childrenMap.update(current => this.computeChildrenMap(this.value(), current, true));
    });
  }
  getChild(key) {
    this.ensureChildrenMap();
    const strKey = key.toString();
    let reader = untracked(this.childrenMap)?.byPropertyKey.get(strKey)?.reader;
    if (!reader) {
      reader = this.createReader(strKey);
    }
    return reader();
  }
  reduceChildren(initialValue, fn, shortCircuit) {
    const map = this.childrenMap();
    if (!map) {
      return initialValue;
    }
    let value = initialValue;
    for (const child of map.byPropertyKey.values()) {
      if (shortCircuit?.(value)) {
        break;
      }
      value = fn(untracked(child.reader), value);
    }
    return value;
  }
  destroy() {
    this.injector.destroy();
  }
  createKeyOrOrphanSignals(kind, identityInParent, initialKeyInParent) {
    if (kind === 'root') {
      return {
        keyInParent: ROOT_KEY_IN_PARENT,
        isOrphaned: FALSE_SIGNAL
      };
    }
    const parent = this.parent;
    let lastKnownKey = initialKeyInParent;
    const keyOrOrphan = computed(() => {
      if (parent.structure.isOrphaned()) {
        return ORPHAN_TOKEN;
      }
      const map = parent.structure.childrenMap();
      if (!map) {
        return ORPHAN_TOKEN;
      }
      const lastKnownChild = map.byPropertyKey.get(lastKnownKey);
      if (lastKnownChild && lastKnownChild.node === this.node) {
        return lastKnownKey;
      }
      if (identityInParent === undefined) {
        return ORPHAN_TOKEN;
      } else {
        for (const [key, child] of map.byPropertyKey) {
          if (child.node === this.node) {
            return lastKnownKey = key;
          }
        }
        return ORPHAN_TOKEN;
      }
    }, ...(ngDevMode ? [{
      debugName: "keyOrOrphan"
    }] : []));
    const isOrphaned = computed(() => keyOrOrphan() === ORPHAN_TOKEN, ...(ngDevMode ? [{
      debugName: "isOrphaned"
    }] : []));
    const keyInParent = computed(() => {
      const key = keyOrOrphan();
      if (key === ORPHAN_TOKEN) {
        if (identityInParent === undefined) {
          throw new _RuntimeError(-1902, ngDevMode && `Orphan field, looking for property '${initialKeyInParent}' of ${getDebugName(parent)}`);
        } else {
          throw new _RuntimeError(1904, ngDevMode && `Orphan field, can't find element in array ${getDebugName(parent)}`);
        }
      }
      return key;
    }, ...(ngDevMode ? [{
      debugName: "keyInParent"
    }] : []));
    return {
      keyInParent,
      isOrphaned
    };
  }
  createChildrenMap() {
    return linkedSignal({
      source: this.value,
      computation: (value, previous) => this.computeChildrenMap(value, previous?.value, false)
    });
  }
  computeChildrenMap(value, prevData, forceMaterialize) {
    if (!isObject(value)) {
      return undefined;
    }
    if (!forceMaterialize && prevData === undefined) {
      if (!(this._anyChildHasLogic ??= this.logic.anyChildHasLogic())) {
        return undefined;
      }
    }
    prevData ??= {
      byPropertyKey: new Map()
    };
    let materializedChildren;
    const parentIsArray = isArray(value);
    if (prevData !== undefined) {
      if (parentIsArray) {
        materializedChildren = maybeRemoveStaleArrayFields(prevData, value, this.identitySymbol);
      } else {
        materializedChildren = maybeRemoveStaleObjectFields(prevData, value);
      }
    }
    for (const key of Object.keys(value)) {
      let trackingKey = undefined;
      const childValue = value[key];
      if (childValue === undefined) {
        if (prevData.byPropertyKey.has(key)) {
          materializedChildren ??= {
            ...prevData
          };
          materializedChildren.byPropertyKey.delete(key);
        }
        continue;
      }
      if (parentIsArray && isObject(childValue) && !isArray(childValue)) {
        trackingKey = childValue[this.identitySymbol] ??= Symbol(ngDevMode ? `id:${globalId++}` : '');
      }
      let childNode;
      if (trackingKey) {
        if (!prevData.byTrackingKey?.has(trackingKey)) {
          materializedChildren ??= {
            ...prevData
          };
          materializedChildren.byTrackingKey ??= new Map();
          materializedChildren.byTrackingKey.set(trackingKey, this.createChildNode(key, trackingKey, parentIsArray));
        }
        childNode = (materializedChildren ?? prevData).byTrackingKey.get(trackingKey);
      }
      const child = prevData.byPropertyKey.get(key);
      if (child === undefined) {
        materializedChildren ??= {
          ...prevData
        };
        materializedChildren.byPropertyKey.set(key, {
          reader: this.createReader(key),
          node: childNode ?? this.createChildNode(key, trackingKey, parentIsArray)
        });
      } else if (childNode && childNode !== child.node) {
        materializedChildren ??= {
          ...prevData
        };
        child.node = childNode;
      }
    }
    return materializedChildren ?? prevData;
  }
  createReader(key) {
    return computed(() => this.childrenMap()?.byPropertyKey.get(key)?.node);
  }
}
class RootFieldNodeStructure extends FieldNodeStructure {
  fieldManager;
  value;
  get parent() {
    return undefined;
  }
  get root() {
    return this.node;
  }
  get pathKeys() {
    return ROOT_PATH_KEYS;
  }
  get keyInParent() {
    return ROOT_KEY_IN_PARENT;
  }
  isOrphaned = FALSE_SIGNAL;
  childrenMap;
  constructor(node, logic, fieldManager, value, createChildNode) {
    super(logic, node, createChildNode);
    this.fieldManager = fieldManager;
    this.value = value;
    this.childrenMap = this.createChildrenMap();
  }
}
class ChildFieldNodeStructure extends FieldNodeStructure {
  logic;
  parent;
  root;
  pathKeys;
  keyInParent;
  value;
  childrenMap;
  isOrphaned;
  get fieldManager() {
    return this.root.structure.fieldManager;
  }
  constructor(node, logic, parent, identityInParent, initialKeyInParent, createChildNode) {
    super(logic, node, createChildNode);
    this.logic = logic;
    this.parent = parent;
    this.root = this.parent.structure.root;
    const signals = this.createKeyOrOrphanSignals('child', identityInParent, initialKeyInParent);
    this.isOrphaned = signals.isOrphaned;
    this.keyInParent = signals.keyInParent;
    this.pathKeys = computed(() => [...parent.structure.pathKeys(), this.keyInParent()], ...(ngDevMode ? [{
      debugName: "pathKeys"
    }] : []));
    this.value = deepSignal(this.parent.structure.value, this.keyInParent);
    this.childrenMap = this.createChildrenMap();
    this.fieldManager.structures.add(this);
  }
}
let globalId = 0;
const ROOT_PATH_KEYS = computed(() => [], ...(ngDevMode ? [{
  debugName: "ROOT_PATH_KEYS"
}] : []));
const ROOT_KEY_IN_PARENT = computed(() => {
  throw new _RuntimeError(1905, ngDevMode && 'The top-level field in the form has no parent.');
}, ...(ngDevMode ? [{
  debugName: "ROOT_KEY_IN_PARENT"
}] : []));
function getDebugName(node) {
  return `<root>.${node.structure.pathKeys().join('.')}`;
}
function maybeRemoveStaleArrayFields(prevData, value, identitySymbol) {
  let data;
  const oldKeys = new Set(prevData.byPropertyKey.keys());
  const oldTracking = prevData.byTrackingKey && new Set(prevData.byTrackingKey.keys());
  for (let i = 0; i < value.length; i++) {
    const childValue = value[i];
    oldKeys.delete(i.toString());
    if (oldTracking && isObject(childValue) && Object.hasOwn(childValue, identitySymbol)) {
      oldTracking.delete(childValue[identitySymbol]);
    }
  }
  if (oldKeys.size > 0) {
    data ??= {
      ...prevData
    };
    for (const key of oldKeys) {
      data.byPropertyKey.delete(key);
    }
  }
  if (oldTracking && oldTracking.size > 0) {
    data ??= {
      ...prevData
    };
    for (const id of oldTracking) {
      data.byTrackingKey.delete(id);
    }
  }
  return data;
}
function maybeRemoveStaleObjectFields(prevData, value) {
  let data;
  for (const key of prevData.byPropertyKey.keys()) {
    if (!Object.hasOwn(value, key)) {
      data ??= {
        ...prevData
      };
      data.byPropertyKey.delete(key);
    }
  }
  return data;
}

class FieldSubmitState {
  node;
  selfSubmitting = signal(false, ...(ngDevMode ? [{
    debugName: "selfSubmitting"
  }] : []));
  submissionErrors;
  constructor(node) {
    this.node = node;
    this.submissionErrors = linkedSignal({
      ...(ngDevMode ? {
        debugName: "submissionErrors"
      } : {}),
      source: this.node.structure.value,
      computation: () => []
    });
  }
  submitting = computed(() => {
    return this.selfSubmitting() || (this.node.structure.parent?.submitting() ?? false);
  }, ...(ngDevMode ? [{
    debugName: "submitting"
  }] : []));
}

class FieldNode {
  structure;
  validationState;
  metadataState;
  nodeState;
  submitState;
  fieldAdapter;
  controlValue;
  _context = undefined;
  get context() {
    return this._context ??= new FieldNodeContext(this);
  }
  fieldProxy = new Proxy(() => this, FIELD_PROXY_HANDLER);
  pathNode;
  constructor(options) {
    this.pathNode = options.pathNode;
    this.fieldAdapter = options.fieldAdapter;
    this.structure = this.fieldAdapter.createStructure(this, options);
    this.validationState = this.fieldAdapter.createValidationState(this, options);
    this.nodeState = this.fieldAdapter.createNodeState(this, options);
    this.metadataState = new FieldMetadataState(this);
    this.submitState = new FieldSubmitState(this);
    this.controlValue = this.controlValueSignal();
    this.metadataState.runMetadataCreateLifecycle();
  }
  focusBoundControl(options) {
    this.getBindingForFocus()?.focus(options);
  }
  getBindingForFocus() {
    const own = this.formFieldBindings().filter(b => b.focus !== undefined).reduce(firstInDom, undefined);
    if (own) return own;
    return this.structure.children().map(child => child.getBindingForFocus()).reduce(firstInDom, undefined);
  }
  pendingSync = linkedSignal({
    ...(ngDevMode ? {
      debugName: "pendingSync"
    } : {}),
    source: () => this.value(),
    computation: (_source, previous) => {
      previous?.value?.abort();
      return undefined;
    }
  });
  get fieldTree() {
    return this.fieldProxy;
  }
  get logicNode() {
    return this.structure.logic;
  }
  get value() {
    return this.structure.value;
  }
  get keyInParent() {
    return this.structure.keyInParent;
  }
  get errors() {
    return this.validationState.errors;
  }
  get parseErrors() {
    return this.validationState.parseErrors;
  }
  get errorSummary() {
    return this.validationState.errorSummary;
  }
  get pending() {
    return this.validationState.pending;
  }
  get valid() {
    return this.validationState.valid;
  }
  get invalid() {
    return this.validationState.invalid;
  }
  get dirty() {
    return this.nodeState.dirty;
  }
  get touched() {
    return this.nodeState.touched;
  }
  get disabled() {
    return this.nodeState.disabled;
  }
  get disabledReasons() {
    return this.nodeState.disabledReasons;
  }
  get hidden() {
    return this.nodeState.hidden;
  }
  get readonly() {
    return this.nodeState.readonly;
  }
  get formFieldBindings() {
    return this.nodeState.formFieldBindings;
  }
  get submitting() {
    return this.submitState.submitting;
  }
  get name() {
    return this.nodeState.name;
  }
  get max() {
    const maxKey = this.metadata(MAX)?.();
    return maxKey ? this.metadata(maxKey) : undefined;
  }
  get maxLength() {
    return this.metadata(MAX_LENGTH);
  }
  get min() {
    const minKey = this.metadata(MIN)?.();
    return minKey ? this.metadata(minKey) : undefined;
  }
  get minLength() {
    return this.metadata(MIN_LENGTH);
  }
  get pattern() {
    return this.metadata(PATTERN) ?? EMPTY;
  }
  get required() {
    return this.metadata(REQUIRED) ?? FALSE;
  }
  metadata(key) {
    return this.metadataState.get(key);
  }
  getError(kind) {
    return this.errors().find(e => e.kind === kind);
  }
  hasMetadata(key) {
    return this.metadataState.has(key);
  }
  markAsTouched(options) {
    if (this.structure.isOrphaned()) {
      return;
    }
    untracked(() => {
      this.markAsTouchedInternal(options);
      this.flushSync();
    });
  }
  markAsTouchedInternal(options) {
    if (this.structure.isOrphaned()) {
      return;
    }
    if (this.validationState.shouldSkipValidation()) {
      return;
    }
    this.nodeState.markAsTouched();
    if (options?.skipDescendants) {
      return;
    }
    for (const child of this.structure.children()) {
      child.markAsTouchedInternal();
    }
  }
  markAsDirty() {
    this.nodeState.markAsDirty();
  }
  markAsPristine() {
    this.nodeState.markAsPristine();
  }
  markAsUntouched() {
    this.nodeState.markAsUntouched();
  }
  reset(value) {
    untracked(() => this._reset(value));
  }
  _reset(value) {
    this.pendingSync()?.abort();
    if (value !== undefined) {
      this.value.set(value);
    }
    this.controlValue.rawSet(this.value());
    this.nodeState.markAsUntouched();
    this.nodeState.markAsPristine();
    for (const binding of this.formFieldBindings()) {
      binding.reset();
    }
    for (const child of this.structure.materializedChildren()) {
      child._reset();
    }
  }
  reloadValidation() {
    untracked(() => this._reloadValidation());
  }
  _reloadValidation() {
    const keys = this.logicNode.logic.getMetadataKeys();
    for (const key of keys) {
      if (key[IS_ASYNC_VALIDATION_RESOURCE]) {
        const resource = this.metadata(key);
        resource.reload?.();
      }
    }
    for (const child of this.structure.children()) {
      child._reloadValidation();
    }
  }
  controlValueSignal() {
    const controlValue = linkedSignal(this.value);
    controlValue.rawSet = controlValue.set;
    controlValue.set = newValue => {
      controlValue.rawSet(newValue);
      this.markAsDirty();
      this.debounceSync();
    };
    const rawUpdate = controlValue.update;
    controlValue.update = updateFn => {
      rawUpdate(updateFn);
      this.markAsDirty();
      this.debounceSync();
    };
    return controlValue;
  }
  sync() {
    this.value.set(this.controlValue());
  }
  flushSync() {
    const pending = this.pendingSync();
    if (pending && !pending.signal.aborted) {
      pending.abort();
      this.sync();
    }
  }
  async debounceSync() {
    const debouncer = untracked(() => {
      this.pendingSync()?.abort();
      return this.nodeState.debouncer();
    });
    if (debouncer) {
      const controller = new AbortController();
      const promise = debouncer(controller.signal);
      if (promise) {
        this.pendingSync.set(controller);
        await promise;
        if (controller.signal.aborted) {
          return;
        }
      }
    }
    if (this.structure.isOrphaned()) {
      return;
    }
    this.sync();
  }
  static newRoot(fieldManager, value, pathNode, adapter) {
    return adapter.newRoot(fieldManager, value, pathNode, adapter);
  }
  createStructure(options) {
    return options.kind === 'root' ? new RootFieldNodeStructure(this, options.logic, options.fieldManager, options.value, this.newChild.bind(this)) : new ChildFieldNodeStructure(this, options.logic, options.parent, options.identityInParent, options.initialKeyInParent, this.newChild.bind(this));
  }
  newChild(key, trackingId, isArray) {
    let childPath;
    let childLogic;
    if (isArray) {
      childPath = this.pathNode.getChild(DYNAMIC);
      childLogic = this.structure.logic.getChild(DYNAMIC);
    } else {
      childPath = this.pathNode.getChild(key);
      childLogic = this.structure.logic.getChild(key);
    }
    return this.fieldAdapter.newChild({
      kind: 'child',
      parent: this,
      pathNode: childPath,
      logic: childLogic,
      initialKeyInParent: key,
      identityInParent: trackingId,
      fieldAdapter: this.fieldAdapter
    });
  }
}
const EMPTY = computed(() => [], ...(ngDevMode ? [{
  debugName: "EMPTY"
}] : []));
const FALSE = computed(() => false, ...(ngDevMode ? [{
  debugName: "FALSE"
}] : []));
function firstInDom(a, b) {
  if (!a) return b;
  if (!b) return a;
  const position = a.element.compareDocumentPosition(b.element);
  return position & Node.DOCUMENT_POSITION_PRECEDING ? b : a;
}

class FieldNodeState {
  node;
  selfTouched = signal(false, ...(ngDevMode ? [{
    debugName: "selfTouched"
  }] : []));
  selfDirty = signal(false, ...(ngDevMode ? [{
    debugName: "selfDirty"
  }] : []));
  markAsTouched() {
    this.selfTouched.set(true);
  }
  markAsDirty() {
    this.selfDirty.set(true);
  }
  markAsPristine() {
    this.selfDirty.set(false);
  }
  markAsUntouched() {
    this.selfTouched.set(false);
  }
  formFieldBindings = signal([], ...(ngDevMode ? [{
    debugName: "formFieldBindings"
  }] : []));
  constructor(node) {
    this.node = node;
  }
  dirty = computed(() => {
    const selfDirtyValue = this.selfDirty() && !this.isNonInteractive();
    return this.node.structure.reduceChildren(selfDirtyValue, (child, value) => value || child.nodeState.dirty(), shortCircuitTrue);
  }, ...(ngDevMode ? [{
    debugName: "dirty"
  }] : []));
  touched = computed(() => {
    const selfTouchedValue = this.selfTouched() && !this.isNonInteractive();
    return this.node.structure.reduceChildren(selfTouchedValue, (child, value) => value || child.nodeState.touched(), shortCircuitTrue);
  }, ...(ngDevMode ? [{
    debugName: "touched"
  }] : []));
  disabledReasons = computed(() => [...(this.node.structure.parent?.nodeState.disabledReasons() ?? []), ...this.node.logicNode.logic.disabledReasons.compute(this.node.context)], {
    ...(ngDevMode ? {
      debugName: "disabledReasons"
    } : {}),
    equal: shallowArrayEquals
  });
  disabled = computed(() => !!this.disabledReasons().length, ...(ngDevMode ? [{
    debugName: "disabled"
  }] : []));
  readonly = computed(() => (this.node.structure.parent?.nodeState.readonly() || this.node.logicNode.logic.readonly.compute(this.node.context)) ?? false, ...(ngDevMode ? [{
    debugName: "readonly"
  }] : []));
  hidden = computed(() => (this.node.structure.parent?.nodeState.hidden() || this.node.logicNode.logic.hidden.compute(this.node.context)) ?? false, ...(ngDevMode ? [{
    debugName: "hidden"
  }] : []));
  name = computed(() => {
    const parent = this.node.structure.parent;
    if (!parent) {
      return this.node.structure.fieldManager.rootName;
    }
    return `${parent.name()}.${this.node.structure.keyInParent()}`;
  }, ...(ngDevMode ? [{
    debugName: "name"
  }] : []));
  debouncer = computed(() => {
    if (this.node.logicNode.logic.hasMetadata(DEBOUNCER)) {
      const debouncerLogic = this.node.logicNode.logic.getMetadata(DEBOUNCER);
      const debouncer = debouncerLogic.compute(this.node.context);
      if (debouncer) {
        return signal => debouncer(this.node.context, signal);
      }
    }
    return this.node.structure.parent?.nodeState.debouncer?.();
  }, ...(ngDevMode ? [{
    debugName: "debouncer"
  }] : []));
  isNonInteractive = computed(() => this.hidden() || this.disabled() || this.readonly(), ...(ngDevMode ? [{
    debugName: "isNonInteractive"
  }] : []));
}

class BasicFieldAdapter {
  newRoot(fieldManager, value, pathNode, adapter) {
    return new FieldNode({
      kind: 'root',
      fieldManager,
      value,
      pathNode,
      logic: pathNode.builder.build(),
      fieldAdapter: adapter
    });
  }
  newChild(options) {
    return new FieldNode(options);
  }
  createNodeState(node) {
    return new FieldNodeState(node);
  }
  createValidationState(node) {
    return new FieldValidationState(node);
  }
  createStructure(node, options) {
    return node.createStructure(options);
  }
}

class FormFieldManager {
  injector;
  rootName;
  submitOptions;
  constructor(injector, rootName, submitOptions) {
    this.injector = injector;
    this.rootName = rootName ?? `${this.injector.get(APP_ID)}.form${nextFormId++}`;
    this.submitOptions = submitOptions;
  }
  structures = new Set();
  createFieldManagementEffect(root) {
    effect(() => {
      const liveStructures = new Set();
      this.markStructuresLive(root, liveStructures);
      for (const structure of this.structures) {
        if (!liveStructures.has(structure)) {
          this.structures.delete(structure);
          untracked(() => structure.destroy());
        }
      }
    }, {
      injector: this.injector
    });
  }
  markStructuresLive(structure, liveStructures) {
    liveStructures.add(structure);
    for (const child of structure.children()) {
      this.markStructuresLive(child.structure, liveStructures);
    }
  }
}
let nextFormId = 0;

const REGISTER_WEBMCP_FORM = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'REGISTER_WEBMCP_FORM' : '');

function normalizeFormArgs(args) {
  let model;
  let schema;
  let options;
  if (args.length === 3) {
    [model, schema, options] = args;
  } else if (args.length === 2) {
    if (isSchemaOrSchemaFn(args[1])) {
      [model, schema] = args;
    } else {
      [model, options] = args;
    }
  } else {
    [model] = args;
  }
  return [model, schema, options];
}

function form(...args) {
  const [model, schema, options] = normalizeFormArgs(args);
  const injector = options?.injector ?? inject(Injector);
  const pathNode = runInInjectionContext(injector, () => SchemaImpl.rootCompile(schema));
  const fieldManager = new FormFieldManager(injector, options?.name, options?.submission);
  const adapter = options?.adapter ?? new BasicFieldAdapter();
  const fieldRoot = FieldNode.newRoot(fieldManager, model, pathNode, adapter);
  fieldManager.createFieldManagementEffect(fieldRoot.structure);
  const {
    experimentalWebMcpTool
  } = options ?? {};
  if (experimentalWebMcpTool) {
    const registerWebMcpForm = runInInjectionContext(injector, () => inject(REGISTER_WEBMCP_FORM, {
      optional: true
    }));
    if (registerWebMcpForm) {
      runInInjectionContext(injector, () => registerWebMcpForm(fieldRoot.fieldTree, {
        name: experimentalWebMcpTool.name,
        description: experimentalWebMcpTool.description
      }));
    } else {
      if (typeof ngDevMode !== 'undefined' && ngDevMode) {
        throw new Error(`Cannot register form "${experimentalWebMcpTool.name}" as a WebMCP tool. ` + `Make sure to use \`provideExperimentalWebMcpForms()\` in your application bootstrap configuration.`);
      }
    }
  }
  return fieldRoot.fieldTree;
}
function applyEach(path, schema) {
  assertPathIsCurrent(path);
  const elementPath = FieldPathNode.unwrapFieldPath(path).getChild(DYNAMIC).fieldPathProxy;
  apply(elementPath, schema);
}
function apply(path, schema) {
  assertPathIsCurrent(path);
  const pathNode = FieldPathNode.unwrapFieldPath(path);
  pathNode.mergeIn(SchemaImpl.create(schema));
}
function applyWhen(path, logic, schema) {
  assertPathIsCurrent(path);
  const pathNode = FieldPathNode.unwrapFieldPath(path);
  pathNode.mergeIn(SchemaImpl.create(schema), {
    fn: logic,
    path
  });
}
function applyWhenValue(path, predicate, schema) {
  applyWhen(path, ({
    value
  }) => predicate(value()), schema);
}
async function submit(form, options) {
  const node = untracked(form);
  if (untracked(node.submitState.submitting)) {
    return false;
  }
  const field = options === undefined ? node.structure.root.fieldProxy : form;
  const detail = {
    root: node.structure.root.fieldProxy,
    submitted: form
  };
  options = typeof options === 'function' ? {
    action: options
  } : options ?? node.structure.fieldManager.submitOptions;
  const action = options?.action;
  if (!action) {
    throw new _RuntimeError(1915, (typeof ngDevMode === 'undefined' || ngDevMode) && 'Cannot submit form with no submit action. Specify the action when creating the form, or as an additional argument to `submit()`.');
  }
  node.markAsTouched();
  const onInvalid = options?.onInvalid;
  const shouldRun = shouldRunAction(node, options?.ignoreValidators);
  try {
    if (shouldRun) {
      node.submitState.selfSubmitting.set(true);
      const errors = await untracked(() => action?.(field, detail));
      errors && setSubmissionErrors(node, errors);
      return !errors || isArray(errors) && errors.length === 0;
    } else {
      untracked(() => onInvalid?.(field, detail));
    }
    return false;
  } finally {
    node.submitState.selfSubmitting.set(false);
  }
}
function schema(fn) {
  return SchemaImpl.create(fn);
}
function shouldRunAction(node, ignoreValidators) {
  switch (ignoreValidators) {
    case 'all':
      return true;
    case 'none':
      return untracked(node.valid);
    default:
      return !untracked(node.invalid);
  }
}
function setSubmissionErrors(submittedField, errors) {
  if (!isArray(errors)) {
    errors = [errors];
  }
  const errorsByField = new Map();
  for (const error of errors) {
    const errorWithField = addDefaultField(error, submittedField.fieldTree);
    const field = errorWithField.fieldTree();
    let fieldErrors = errorsByField.get(field);
    if (!fieldErrors) {
      fieldErrors = [];
      errorsByField.set(field, fieldErrors);
    }
    fieldErrors.push(errorWithField);
  }
  for (const [field, fieldErrors] of errorsByField) {
    field.submitState.submissionErrors.set(fieldErrors);
  }
}

class CompatValidationError {
  kind = 'compat';
  control;
  fieldTree;
  context;
  message;
  constructor({
    context,
    kind,
    control
  }) {
    this.context = context;
    this.kind = kind;
    this.control = control;
  }
}
function signalErrorsToValidationErrors(errors) {
  if (errors.length === 0) {
    return null;
  }
  const errObj = {};
  for (const error of errors) {
    errObj[error.kind] = error instanceof CompatValidationError ? error.context : error;
  }
  return errObj;
}
function reactiveErrorsToSignalErrors(errors, control) {
  if (errors === null) {
    return [];
  }
  return Object.entries(errors).map(([kind, context]) => {
    return new CompatValidationError({
      context,
      kind,
      control
    });
  });
}
function extractNestedReactiveErrors(control) {
  const errors = [];
  if (control.errors) {
    errors.push(...reactiveErrorsToSignalErrors(control.errors, control));
  }
  if (control instanceof FormGroup || control instanceof FormArray) {
    for (const c of Object.values(control.controls)) {
      errors.push(...extractNestedReactiveErrors(c));
    }
  }
  return errors;
}

export { BasicFieldAdapter, CompatValidationError, DEBOUNCER, FIELD_TREE, FieldNode, FieldNodeState, FieldNodeStructure, FieldPathNode, IS_ASYNC_VALIDATION_RESOURCE, MAX, MAX_DATE, MAX_LENGTH, MAX_NUMBER, MIN, MIN_DATE, MIN_LENGTH, MIN_NUMBER, MetadataKey, MetadataReducer, PATTERN, REGISTER_WEBMCP_FORM, REQUIRED, addDefaultField, apply, applyEach, applyWhen, applyWhenValue, assertPathIsCurrent, calculateValidationSelfStatus, createLimitSelectionKey, createManagedMetadataKey, createMetadataKey, extractNestedReactiveErrors, form, getInjectorFromOptions, isArray, isObject, metadata, normalizeFormArgs, reactiveErrorsToSignalErrors, schema, shallowArrayEquals, signalErrorsToValidationErrors, submit };
//# sourceMappingURL=_validation_errors-chunk.mjs.map