@angular/forms
Advanced tools
| /** | ||
| * @license Angular v21.0.0-rc.3 | ||
| * (c) 2010-2025 Google LLC. https://angular.dev/ | ||
| * License: MIT | ||
| */ | ||
| import { computed, untracked, runInInjectionContext, linkedSignal, Injector, signal, APP_ID, effect, inject } from '@angular/core'; | ||
| import { AbstractControl } from '@angular/forms'; | ||
| import { SIGNAL } from '@angular/core/primitives/signals'; | ||
| function isArray(value) { | ||
| return Array.isArray(value); | ||
| } | ||
| function isObject(value) { | ||
| return (typeof value === 'object' || typeof value === 'function') && value != null; | ||
| } | ||
| function reduceChildren(node, initialValue, fn, shortCircuit) { | ||
| const childrenMap = node.structure.childrenMap(); | ||
| if (!childrenMap) { | ||
| return initialValue; | ||
| } | ||
| let value = initialValue; | ||
| for (const child of childrenMap.values()) { | ||
| if (shortCircuit?.(value)) { | ||
| break; | ||
| } | ||
| value = fn(child, value); | ||
| } | ||
| return value; | ||
| } | ||
| 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 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" | ||
| }] : [])); | ||
| syncErrors = computed(() => { | ||
| if (this.shouldSkipValidation()) { | ||
| return []; | ||
| } | ||
| return [...this.node.logicNode.logic.syncErrors.compute(this.node.context), ...this.syncTreeErrors(), ...normalizeErrors(this.node.submitState.serverErrors())]; | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "syncErrors" | ||
| }] : [])); | ||
| syncValid = computed(() => { | ||
| if (this.shouldSkipValidation()) { | ||
| return true; | ||
| } | ||
| return reduceChildren(this.node, this.syncErrors().length === 0, (child, value) => value && child.validationState.syncValid(), shortCircuitFalse); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "syncValid" | ||
| }] : [])); | ||
| syncTreeErrors = computed(() => this.rawSyncTreeErrors().filter(err => err.field === this.node.fieldProxy), ...(ngDevMode ? [{ | ||
| debugName: "syncTreeErrors" | ||
| }] : [])); | ||
| 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" | ||
| }] : [])); | ||
| asyncErrors = computed(() => { | ||
| if (this.shouldSkipValidation()) { | ||
| return []; | ||
| } | ||
| return this.rawAsyncErrors().filter(err => err === 'pending' || err.field === this.node.fieldProxy); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "asyncErrors" | ||
| }] : [])); | ||
| errors = computed(() => [...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], ...(ngDevMode ? [{ | ||
| debugName: "errors" | ||
| }] : [])); | ||
| errorSummary = computed(() => reduceChildren(this.node, this.errors(), (child, result) => [...result, ...child.errorSummary()]), ...(ngDevMode ? [{ | ||
| debugName: "errorSummary" | ||
| }] : [])); | ||
| pending = computed(() => reduceChildren(this.node, this.asyncErrors().includes('pending'), (child, value) => value || child.validationState.asyncErrors().includes('pending')), ...(ngDevMode ? [{ | ||
| debugName: "pending" | ||
| }] : [])); | ||
| status = computed(() => { | ||
| if (this.shouldSkipValidation()) { | ||
| return 'valid'; | ||
| } | ||
| let ownStatus = calculateValidationSelfStatus(this); | ||
| return reduceChildren(this.node, 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(), ...(ngDevMode ? [{ | ||
| debugName: "shouldSkipValidation" | ||
| }] : [])); | ||
| } | ||
| function normalizeErrors(error) { | ||
| if (error === undefined) { | ||
| return []; | ||
| } | ||
| if (isArray(error)) { | ||
| return error; | ||
| } | ||
| return [error]; | ||
| } | ||
| function addDefaultField(errors, field) { | ||
| if (isArray(errors)) { | ||
| for (const error of errors) { | ||
| error.field ??= field; | ||
| } | ||
| } else if (errors) { | ||
| errors.field ??= field; | ||
| } | ||
| return errors; | ||
| } | ||
| let boundPathDepth = 0; | ||
| function getBoundPathDepth() { | ||
| return boundPathDepth; | ||
| } | ||
| function setBoundPathDepthForResolution(fn, depth) { | ||
| return (...args) => { | ||
| try { | ||
| boundPathDepth = depth; | ||
| return fn(...args); | ||
| } finally { | ||
| boundPathDepth = 0; | ||
| } | ||
| }; | ||
| } | ||
| 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); | ||
| } | ||
| } | ||
| 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 AggregateMetadataMergeLogic extends AbstractLogic { | ||
| key; | ||
| get defaultValue() { | ||
| return this.key.getInitial(); | ||
| } | ||
| constructor(predicates, key) { | ||
| super(predicates); | ||
| this.key = key; | ||
| } | ||
| compute(ctx) { | ||
| if (this.fns.length === 0) { | ||
| return this.key.getInitial(); | ||
| } | ||
| let acc = this.key.getInitial(); | ||
| for (let i = 0; i < this.fns.length; i++) { | ||
| const item = this.fns[i](ctx); | ||
| if (item !== IGNORED) { | ||
| acc = this.key.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; | ||
| aggregateMetadataKeys = new Map(); | ||
| metadataFactories = 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); | ||
| } | ||
| hasAggregateMetadata(key) { | ||
| return this.aggregateMetadataKeys.has(key); | ||
| } | ||
| getAggregateMetadataEntries() { | ||
| return this.aggregateMetadataKeys.entries(); | ||
| } | ||
| getMetadataFactoryEntries() { | ||
| return this.metadataFactories.entries(); | ||
| } | ||
| getAggregateMetadata(key) { | ||
| if (!this.aggregateMetadataKeys.has(key)) { | ||
| this.aggregateMetadataKeys.set(key, new AggregateMetadataMergeLogic(this.predicates, key)); | ||
| } | ||
| return this.aggregateMetadataKeys.get(key); | ||
| } | ||
| addMetadataFactory(key, factory) { | ||
| if (this.metadataFactories.has(key)) { | ||
| throw new Error(`Can't define value twice for the same MetadataKey`); | ||
| } | ||
| this.metadataFactories.set(key, factory); | ||
| } | ||
| 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, metadataLogic] of other.getAggregateMetadataEntries()) { | ||
| this.getAggregateMetadata(key).mergeIn(metadataLogic); | ||
| } | ||
| for (const [key, metadataFactory] of other.getMetadataFactoryEntries()) { | ||
| this.addMetadataFactory(key, metadataFactory); | ||
| } | ||
| } | ||
| } | ||
| 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); | ||
| } | ||
| addAggregateMetadataRule(key, logic) { | ||
| this.getCurrent().addAggregateMetadataRule(key, logic); | ||
| } | ||
| addMetadataFactory(key, factory) { | ||
| this.getCurrent().addMetadataFactory(key, factory); | ||
| } | ||
| 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)); | ||
| } | ||
| 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)); | ||
| } | ||
| addAggregateMetadataRule(key, logic) { | ||
| this.logic.getAggregateMetadata(key).push(setBoundPathDepthForResolution(logic, this.depth)); | ||
| } | ||
| addMetadataFactory(key, factory) { | ||
| this.logic.addMetadataFactory(key, setBoundPathDepthForResolution(factory, 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; | ||
| } | ||
| } | ||
| 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) { | ||
| return this.builder?.hasLogic(builder) ?? 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)); | ||
| } | ||
| } | ||
| 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 Error('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 Error('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 Error(`A FieldPath can only be used directly within the Schema that owns it,` + ` **not** outside of it or within a sub-schema.`); | ||
| } | ||
| } | ||
| class MetadataKey { | ||
| brand; | ||
| constructor() {} | ||
| } | ||
| function createMetadataKey() { | ||
| return new MetadataKey(); | ||
| } | ||
| class AggregateMetadataKey { | ||
| reduce; | ||
| getInitial; | ||
| brand; | ||
| constructor(reduce, getInitial) { | ||
| this.reduce = reduce; | ||
| this.getInitial = getInitial; | ||
| } | ||
| } | ||
| function reducedMetadataKey(reduce, getInitial) { | ||
| return new AggregateMetadataKey(reduce, getInitial); | ||
| } | ||
| function listMetadataKey() { | ||
| return reducedMetadataKey((acc, item) => item === undefined ? acc : [...acc, item], () => []); | ||
| } | ||
| function minMetadataKey() { | ||
| return reducedMetadataKey((prev, next) => { | ||
| if (prev === undefined) { | ||
| return next; | ||
| } | ||
| if (next === undefined) { | ||
| return prev; | ||
| } | ||
| return Math.min(prev, next); | ||
| }, () => undefined); | ||
| } | ||
| function maxMetadataKey() { | ||
| return reducedMetadataKey((prev, next) => { | ||
| if (prev === undefined) { | ||
| return next; | ||
| } | ||
| if (next === undefined) { | ||
| return prev; | ||
| } | ||
| return Math.max(prev, next); | ||
| }, () => undefined); | ||
| } | ||
| function orMetadataKey() { | ||
| return reducedMetadataKey((prev, next) => prev || next, () => false); | ||
| } | ||
| function andMetadataKey() { | ||
| return reducedMetadataKey((prev, next) => prev && next, () => true); | ||
| } | ||
| const REQUIRED = orMetadataKey(); | ||
| const MIN = maxMetadataKey(); | ||
| const MAX = minMetadataKey(); | ||
| const MIN_LENGTH = maxMetadataKey(); | ||
| const MAX_LENGTH = minMetadataKey(); | ||
| const PATTERN = listMetadataKey(); | ||
| const DEBOUNCER = reducedMetadataKey((_, item) => item, () => undefined); | ||
| class FieldNodeContext { | ||
| node; | ||
| cache = new WeakMap(); | ||
| constructor(node) { | ||
| this.node = node; | ||
| } | ||
| 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 Error('Path is not part of this field tree.'); | ||
| } | ||
| } | ||
| for (let key of targetPathNode.keys) { | ||
| field = field.structure.getChild(key); | ||
| if (field === undefined) { | ||
| throw new Error(`Cannot resolve path .${targetPathNode.keys.join('.')} relative to field ${['<root>', ...this.node.structure.pathKeys()].join('.')}.`); | ||
| } | ||
| } | ||
| return field.fieldProxy; | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "resolver" | ||
| }] : [])); | ||
| this.cache.set(target, resolver); | ||
| } | ||
| return this.cache.get(target)(); | ||
| } | ||
| get field() { | ||
| 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 Error(`RuntimeError: cannot access index, parent field is not an array`); | ||
| } | ||
| return Number(key); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "index" | ||
| }] : [])); | ||
| fieldTreeOf = p => this.resolve(p); | ||
| stateOf = p => this.resolve(p)(); | ||
| valueOf = p => { | ||
| const result = this.resolve(p)().value(); | ||
| if (result instanceof AbstractControl) { | ||
| throw new Error(`Tried to read an 'AbstractControl' value form a 'form()'. Did you mean to use 'compatForm()' instead?`); | ||
| } | ||
| return result; | ||
| }; | ||
| } | ||
| class FieldMetadataState { | ||
| node; | ||
| metadata = new Map(); | ||
| constructor(node) { | ||
| this.node = node; | ||
| untracked(() => runInInjectionContext(this.node.structure.injector, () => { | ||
| for (const [key, factory] of this.node.logicNode.logic.getMetadataFactoryEntries()) { | ||
| this.metadata.set(key, factory(this.node.context)); | ||
| } | ||
| })); | ||
| } | ||
| get(key) { | ||
| if (key instanceof MetadataKey) { | ||
| return this.metadata.get(key); | ||
| } | ||
| if (!this.metadata.has(key)) { | ||
| const logic = this.node.logicNode.logic.getAggregateMetadata(key); | ||
| const result = computed(() => logic.compute(this.node.context), ...(ngDevMode ? [{ | ||
| debugName: "result" | ||
| }] : [])); | ||
| this.metadata.set(key, result); | ||
| } | ||
| return this.metadata.get(key); | ||
| } | ||
| has(key) { | ||
| if (key instanceof AggregateMetadataKey) { | ||
| return this.node.logicNode.logic.hasAggregateMetadata(key); | ||
| } else { | ||
| return this.metadata.has(key); | ||
| } | ||
| } | ||
| } | ||
| const FIELD_PROXY_HANDLER = { | ||
| get(getTgt, p, receiver) { | ||
| const tgt = getTgt(); | ||
| const child = tgt.structure.getChild(p); | ||
| if (child !== undefined) { | ||
| return child.fieldProxy; | ||
| } | ||
| const value = untracked(tgt.value); | ||
| if (isArray(value)) { | ||
| if (p === 'length') { | ||
| return tgt.value().length; | ||
| } | ||
| if (p === Symbol.iterator) { | ||
| return Array.prototype[p]; | ||
| } | ||
| } | ||
| if (isObject(value)) { | ||
| if (p === 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 => { | ||
| 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 | ||
| }; | ||
| } | ||
| } | ||
| class FieldNodeStructure { | ||
| logic; | ||
| identitySymbol = Symbol(); | ||
| _injector = undefined; | ||
| get injector() { | ||
| this._injector ??= Injector.create({ | ||
| providers: [], | ||
| parent: this.fieldManager.injector | ||
| }); | ||
| return this._injector; | ||
| } | ||
| constructor(logic) { | ||
| this.logic = logic; | ||
| } | ||
| children() { | ||
| return this.childrenMap()?.values() ?? []; | ||
| } | ||
| getChild(key) { | ||
| const map = this.childrenMap(); | ||
| const value = this.value(); | ||
| if (!map || !isObject(value)) { | ||
| return undefined; | ||
| } | ||
| if (isArray(value)) { | ||
| const childValue = value[key]; | ||
| if (isObject(childValue) && childValue.hasOwnProperty(this.identitySymbol)) { | ||
| key = childValue[this.identitySymbol]; | ||
| } | ||
| } | ||
| return map.get(typeof key === 'number' ? key.toString() : key); | ||
| } | ||
| destroy() { | ||
| this.injector.destroy(); | ||
| } | ||
| } | ||
| class RootFieldNodeStructure extends FieldNodeStructure { | ||
| node; | ||
| fieldManager; | ||
| value; | ||
| get parent() { | ||
| return undefined; | ||
| } | ||
| get root() { | ||
| return this.node; | ||
| } | ||
| get pathKeys() { | ||
| return ROOT_PATH_KEYS; | ||
| } | ||
| get keyInParent() { | ||
| return ROOT_KEY_IN_PARENT; | ||
| } | ||
| childrenMap; | ||
| constructor(node, pathNode, logic, fieldManager, value, adapter, createChildNode) { | ||
| super(logic); | ||
| this.node = node; | ||
| this.fieldManager = fieldManager; | ||
| this.value = value; | ||
| this.childrenMap = makeChildrenMapSignal(node, value, this.identitySymbol, pathNode, logic, adapter, createChildNode); | ||
| } | ||
| } | ||
| class ChildFieldNodeStructure extends FieldNodeStructure { | ||
| parent; | ||
| root; | ||
| pathKeys; | ||
| keyInParent; | ||
| value; | ||
| childrenMap; | ||
| get fieldManager() { | ||
| return this.root.structure.fieldManager; | ||
| } | ||
| constructor(node, pathNode, logic, parent, identityInParent, initialKeyInParent, adapter, createChildNode) { | ||
| super(logic); | ||
| this.parent = parent; | ||
| this.root = this.parent.structure.root; | ||
| this.pathKeys = computed(() => [...parent.structure.pathKeys(), this.keyInParent()], ...(ngDevMode ? [{ | ||
| debugName: "pathKeys" | ||
| }] : [])); | ||
| if (identityInParent === undefined) { | ||
| const key = initialKeyInParent; | ||
| this.keyInParent = computed(() => { | ||
| if (parent.structure.childrenMap()?.get(key) !== node) { | ||
| throw new Error(`RuntimeError: orphan field, looking for property '${key}' of ${getDebugName(parent)}`); | ||
| } | ||
| return key; | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "keyInParent" | ||
| }] : [])); | ||
| } else { | ||
| let lastKnownKey = initialKeyInParent; | ||
| this.keyInParent = computed(() => { | ||
| const parentValue = parent.structure.value(); | ||
| if (!isArray(parentValue)) { | ||
| throw new Error(`RuntimeError: orphan field, expected ${getDebugName(parent)} to be an array`); | ||
| } | ||
| const data = parentValue[lastKnownKey]; | ||
| if (isObject(data) && data.hasOwnProperty(parent.structure.identitySymbol) && data[parent.structure.identitySymbol] === identityInParent) { | ||
| return lastKnownKey; | ||
| } | ||
| for (let i = 0; i < parentValue.length; i++) { | ||
| const data = parentValue[i]; | ||
| if (isObject(data) && data.hasOwnProperty(parent.structure.identitySymbol) && data[parent.structure.identitySymbol] === identityInParent) { | ||
| return lastKnownKey = i.toString(); | ||
| } | ||
| } | ||
| throw new Error(`RuntimeError: orphan field, can't find element in array ${getDebugName(parent)}`); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "keyInParent" | ||
| }] : [])); | ||
| } | ||
| this.value = deepSignal(this.parent.structure.value, this.keyInParent); | ||
| this.childrenMap = makeChildrenMapSignal(node, this.value, this.identitySymbol, pathNode, logic, adapter, createChildNode); | ||
| 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 Error(`RuntimeError: the top-level field in the form has no parent`); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "ROOT_KEY_IN_PARENT" | ||
| }] : [])); | ||
| function makeChildrenMapSignal(node, valueSignal, identitySymbol, pathNode, logic, adapter, createChildNode) { | ||
| return linkedSignal({ | ||
| source: valueSignal, | ||
| computation: (value, previous) => { | ||
| let childrenMap = previous?.value; | ||
| if (!isObject(value)) { | ||
| return undefined; | ||
| } | ||
| const isValueArray = isArray(value); | ||
| if (childrenMap !== undefined) { | ||
| let oldKeys = undefined; | ||
| if (isValueArray) { | ||
| oldKeys = new Set(childrenMap.keys()); | ||
| for (let i = 0; i < value.length; i++) { | ||
| const childValue = value[i]; | ||
| if (isObject(childValue) && childValue.hasOwnProperty(identitySymbol)) { | ||
| oldKeys.delete(childValue[identitySymbol]); | ||
| } else { | ||
| oldKeys.delete(i.toString()); | ||
| } | ||
| } | ||
| for (const key of oldKeys) { | ||
| childrenMap.delete(key); | ||
| } | ||
| } else { | ||
| for (let key of childrenMap.keys()) { | ||
| if (!value.hasOwnProperty(key)) { | ||
| childrenMap.delete(key); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| for (let key of Object.keys(value)) { | ||
| let trackingId = undefined; | ||
| const childValue = value[key]; | ||
| if (childValue === undefined) { | ||
| childrenMap?.delete(key); | ||
| continue; | ||
| } | ||
| if (isValueArray && isObject(childValue) && !isArray(childValue)) { | ||
| trackingId = childValue[identitySymbol] ??= Symbol(ngDevMode ? `id:${globalId++}` : ''); | ||
| } | ||
| const identity = trackingId ?? key; | ||
| if (childrenMap?.has(identity)) { | ||
| continue; | ||
| } | ||
| let childPath; | ||
| let childLogic; | ||
| if (isValueArray) { | ||
| childPath = pathNode.getChild(DYNAMIC); | ||
| childLogic = logic.getChild(DYNAMIC); | ||
| } else { | ||
| childPath = pathNode.getChild(key); | ||
| childLogic = logic.getChild(key); | ||
| } | ||
| childrenMap ??= new Map(); | ||
| childrenMap.set(identity, createChildNode({ | ||
| kind: 'child', | ||
| parent: node, | ||
| pathNode: childPath, | ||
| logic: childLogic, | ||
| initialKeyInParent: key, | ||
| identityInParent: trackingId, | ||
| fieldAdapter: adapter | ||
| })); | ||
| } | ||
| return childrenMap; | ||
| }, | ||
| equal: () => false | ||
| }); | ||
| } | ||
| function getDebugName(node) { | ||
| return `<root>.${node.structure.pathKeys().join('.')}`; | ||
| } | ||
| class FieldSubmitState { | ||
| node; | ||
| selfSubmitting = signal(false, ...(ngDevMode ? [{ | ||
| debugName: "selfSubmitting" | ||
| }] : [])); | ||
| serverErrors; | ||
| constructor(node) { | ||
| this.node = node; | ||
| this.serverErrors = linkedSignal(...(ngDevMode ? [{ | ||
| debugName: "serverErrors", | ||
| source: this.node.structure.value, | ||
| computation: () => [] | ||
| }] : [{ | ||
| 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; | ||
| _context = undefined; | ||
| get context() { | ||
| return this._context ??= new FieldNodeContext(this); | ||
| } | ||
| fieldProxy = new Proxy(() => this, FIELD_PROXY_HANDLER); | ||
| constructor(options) { | ||
| 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); | ||
| } | ||
| pendingSync = linkedSignal(...(ngDevMode ? [{ | ||
| debugName: "pendingSync", | ||
| source: () => this.value(), | ||
| computation: (_source, previous) => { | ||
| previous?.value?.abort(); | ||
| return undefined; | ||
| } | ||
| }] : [{ | ||
| source: () => this.value(), | ||
| computation: (_source, previous) => { | ||
| previous?.value?.abort(); | ||
| return undefined; | ||
| } | ||
| }])); | ||
| get logicNode() { | ||
| return this.structure.logic; | ||
| } | ||
| get value() { | ||
| return this.structure.value; | ||
| } | ||
| _controlValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{ | ||
| debugName: "_controlValue" | ||
| }] : [])); | ||
| get controlValue() { | ||
| return this._controlValue.asReadonly(); | ||
| } | ||
| get keyInParent() { | ||
| return this.structure.keyInParent; | ||
| } | ||
| get errors() { | ||
| return this.validationState.errors; | ||
| } | ||
| 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 fieldBindings() { | ||
| return this.nodeState.fieldBindings; | ||
| } | ||
| get submitting() { | ||
| return this.submitState.submitting; | ||
| } | ||
| get name() { | ||
| return this.nodeState.name; | ||
| } | ||
| metadataOrUndefined(key) { | ||
| return this.hasMetadata(key) ? this.metadata(key) : undefined; | ||
| } | ||
| get max() { | ||
| return this.metadataOrUndefined(MAX); | ||
| } | ||
| get maxLength() { | ||
| return this.metadataOrUndefined(MAX_LENGTH); | ||
| } | ||
| get min() { | ||
| return this.metadataOrUndefined(MIN); | ||
| } | ||
| get minLength() { | ||
| return this.metadataOrUndefined(MIN_LENGTH); | ||
| } | ||
| get pattern() { | ||
| return this.metadataOrUndefined(PATTERN) ?? EMPTY; | ||
| } | ||
| get required() { | ||
| return this.metadataOrUndefined(REQUIRED) ?? FALSE; | ||
| } | ||
| metadata(key) { | ||
| return this.metadataState.get(key); | ||
| } | ||
| hasMetadata(key) { | ||
| return this.metadataState.has(key); | ||
| } | ||
| markAsTouched() { | ||
| this.nodeState.markAsTouched(); | ||
| this.pendingSync()?.abort(); | ||
| this.sync(); | ||
| } | ||
| markAsDirty() { | ||
| this.nodeState.markAsDirty(); | ||
| } | ||
| reset() { | ||
| this.nodeState.markAsUntouched(); | ||
| this.nodeState.markAsPristine(); | ||
| for (const child of this.structure.children()) { | ||
| child.reset(); | ||
| } | ||
| } | ||
| setControlValue(newValue) { | ||
| this._controlValue.set(newValue); | ||
| this.markAsDirty(); | ||
| this.debounceSync(); | ||
| } | ||
| sync() { | ||
| this.value.set(this.controlValue()); | ||
| } | ||
| async debounceSync() { | ||
| this.pendingSync()?.abort(); | ||
| const debouncer = 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; | ||
| } | ||
| } | ||
| } | ||
| this.sync(); | ||
| } | ||
| static newRoot(fieldManager, value, pathNode, adapter) { | ||
| return adapter.newRoot(fieldManager, value, pathNode, adapter); | ||
| } | ||
| static newChild(options) { | ||
| return options.fieldAdapter.newChild(options); | ||
| } | ||
| createStructure(options) { | ||
| return options.kind === 'root' ? new RootFieldNodeStructure(this, options.pathNode, options.logic, options.fieldManager, options.value, options.fieldAdapter, FieldNode.newChild) : new ChildFieldNodeStructure(this, options.pathNode, options.logic, options.parent, options.identityInParent, options.initialKeyInParent, options.fieldAdapter, FieldNode.newChild); | ||
| } | ||
| } | ||
| const EMPTY = computed(() => [], ...(ngDevMode ? [{ | ||
| debugName: "EMPTY" | ||
| }] : [])); | ||
| const FALSE = computed(() => false, ...(ngDevMode ? [{ | ||
| debugName: "FALSE" | ||
| }] : [])); | ||
| 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); | ||
| } | ||
| fieldBindings = signal([], ...(ngDevMode ? [{ | ||
| debugName: "fieldBindings" | ||
| }] : [])); | ||
| constructor(node) { | ||
| this.node = node; | ||
| } | ||
| dirty = computed(() => { | ||
| const selfDirtyValue = this.selfDirty() && !this.isNonInteractive(); | ||
| return reduceChildren(this.node, selfDirtyValue, (child, value) => value || child.nodeState.dirty(), shortCircuitTrue); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "dirty" | ||
| }] : [])); | ||
| touched = computed(() => { | ||
| const selfTouchedValue = this.selfTouched() && !this.isNonInteractive(); | ||
| return reduceChildren(this.node, 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" | ||
| }] : [])); | ||
| 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.hasAggregateMetadata(DEBOUNCER)) { | ||
| const debouncerLogic = this.node.logicNode.logic.getAggregateMetadata(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; | ||
| constructor(injector, rootName) { | ||
| this.injector = injector; | ||
| this.rootName = rootName ?? `${this.injector.get(APP_ID)}.form${nextFormId++}`; | ||
| } | ||
| 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; | ||
| 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); | ||
| const adapter = options?.adapter ?? new BasicFieldAdapter(); | ||
| const fieldRoot = FieldNode.newRoot(fieldManager, model, pathNode, adapter); | ||
| fieldManager.createFieldManagementEffect(fieldRoot.structure); | ||
| return fieldRoot.fieldProxy; | ||
| } | ||
| 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, action) { | ||
| const node = form(); | ||
| markAllAsTouched(node); | ||
| if (node.invalid()) { | ||
| return; | ||
| } | ||
| node.submitState.selfSubmitting.set(true); | ||
| try { | ||
| const errors = await action(form); | ||
| errors && setServerErrors(node, errors); | ||
| } finally { | ||
| node.submitState.selfSubmitting.set(false); | ||
| } | ||
| } | ||
| function setServerErrors(submittedField, errors) { | ||
| if (!isArray(errors)) { | ||
| errors = [errors]; | ||
| } | ||
| const errorsByField = new Map(); | ||
| for (const error of errors) { | ||
| const errorWithField = addDefaultField(error, submittedField.fieldProxy); | ||
| const field = errorWithField.field(); | ||
| let fieldErrors = errorsByField.get(field); | ||
| if (!fieldErrors) { | ||
| fieldErrors = []; | ||
| errorsByField.set(field, fieldErrors); | ||
| } | ||
| fieldErrors.push(errorWithField); | ||
| } | ||
| for (const [field, fieldErrors] of errorsByField) { | ||
| field.submitState.serverErrors.set(fieldErrors); | ||
| } | ||
| } | ||
| function schema(fn) { | ||
| return SchemaImpl.create(fn); | ||
| } | ||
| function markAllAsTouched(node) { | ||
| node.markAsTouched(); | ||
| for (const child of node.structure.children()) { | ||
| markAllAsTouched(child); | ||
| } | ||
| } | ||
| export { AggregateMetadataKey, BasicFieldAdapter, DEBOUNCER, FieldNode, FieldNodeState, FieldNodeStructure, FieldPathNode, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MetadataKey, PATTERN, REQUIRED, addDefaultField, andMetadataKey, apply, applyEach, applyWhen, applyWhenValue, assertPathIsCurrent, calculateValidationSelfStatus, createMetadataKey, form, getInjectorFromOptions, isArray, listMetadataKey, maxMetadataKey, minMetadataKey, normalizeFormArgs, orMetadataKey, reducedMetadataKey, schema, submit }; | ||
| //# sourceMappingURL=_structure-chunk.mjs.map |
Sorry, the diff of this file is too big to display
| /** | ||
| * @license Angular v21.0.0-rc.3 | ||
| * (c) 2010-2025 Google LLC. https://angular.dev/ | ||
| * License: MIT | ||
| */ | ||
| import { FieldNode, getInjectorFromOptions, FieldNodeState, FieldNodeStructure, calculateValidationSelfStatus, BasicFieldAdapter, form, normalizeFormArgs } from './_structure-chunk.mjs'; | ||
| import { linkedSignal, untracked, runInInjectionContext, computed, signal } from '@angular/core'; | ||
| import { FormGroup, FormArray, AbstractControl } from '@angular/forms'; | ||
| import { toSignal } from '@angular/core/rxjs-interop'; | ||
| import { ReplaySubject } from 'rxjs'; | ||
| import { map, takeUntil } from 'rxjs/operators'; | ||
| import '@angular/core/primitives/signals'; | ||
| class CompatFieldNode extends FieldNode { | ||
| options; | ||
| control; | ||
| constructor(options) { | ||
| super(options); | ||
| this.options = options; | ||
| this.control = this.options.control; | ||
| } | ||
| } | ||
| function makeCreateDestroySubject() { | ||
| let destroy$ = new ReplaySubject(1); | ||
| return () => { | ||
| if (destroy$) { | ||
| destroy$.next(); | ||
| destroy$.complete(); | ||
| } | ||
| return destroy$ = new ReplaySubject(1); | ||
| }; | ||
| } | ||
| function extractControlPropToSignal(options, makeSignal) { | ||
| const injector = getInjectorFromOptions(options); | ||
| const createDestroySubject = makeCreateDestroySubject(); | ||
| const signalOfControlSignal = linkedSignal(...(ngDevMode ? [{ | ||
| debugName: "signalOfControlSignal", | ||
| source: options.control, | ||
| computation: control => { | ||
| return untracked(() => { | ||
| return runInInjectionContext(injector, () => makeSignal(control, createDestroySubject())); | ||
| }); | ||
| } | ||
| }] : [{ | ||
| source: options.control, | ||
| computation: control => { | ||
| return untracked(() => { | ||
| return runInInjectionContext(injector, () => makeSignal(control, createDestroySubject())); | ||
| }); | ||
| } | ||
| }])); | ||
| return computed(() => signalOfControlSignal()()); | ||
| } | ||
| const getControlStatusSignal = (options, getValue) => { | ||
| return extractControlPropToSignal(options, (c, destroy$) => toSignal(c.statusChanges.pipe(map(() => getValue(c)), takeUntil(destroy$)), { | ||
| initialValue: getValue(c) | ||
| })); | ||
| }; | ||
| const getControlEventsSignal = (options, getValue) => { | ||
| return extractControlPropToSignal(options, (c, destroy$) => toSignal(c.events.pipe(map(() => { | ||
| return getValue(c); | ||
| }), takeUntil(destroy$)), { | ||
| initialValue: getValue(c) | ||
| })); | ||
| }; | ||
| class CompatNodeState extends FieldNodeState { | ||
| compatNode; | ||
| touched; | ||
| dirty; | ||
| disabled; | ||
| control; | ||
| constructor(compatNode, options) { | ||
| super(compatNode); | ||
| this.compatNode = compatNode; | ||
| this.control = options.control; | ||
| this.touched = getControlEventsSignal(options, c => c.touched); | ||
| this.dirty = getControlEventsSignal(options, c => c.dirty); | ||
| const controlDisabled = getControlStatusSignal(options, c => c.disabled); | ||
| this.disabled = computed(() => { | ||
| return controlDisabled() || this.disabledReasons().length > 0; | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "disabled" | ||
| }] : [])); | ||
| } | ||
| markAsDirty() { | ||
| this.control().markAsDirty(); | ||
| } | ||
| markAsTouched() { | ||
| this.control().markAsTouched(); | ||
| } | ||
| markAsPristine() { | ||
| this.control().markAsPristine(); | ||
| } | ||
| markAsUntouched() { | ||
| this.control().markAsUntouched(); | ||
| } | ||
| } | ||
| function getParentFromOptions(options) { | ||
| if (options.kind === 'root') { | ||
| return undefined; | ||
| } | ||
| return options.parent; | ||
| } | ||
| function getFieldManagerFromOptions(options) { | ||
| if (options.kind === 'root') { | ||
| return options.fieldManager; | ||
| } | ||
| return options.parent.structure.root.structure.fieldManager; | ||
| } | ||
| function getControlValueSignal(options) { | ||
| const value = extractControlPropToSignal(options, (control, destroy$) => { | ||
| return toSignal(control.valueChanges.pipe(map(() => control.getRawValue()), takeUntil(destroy$)), { | ||
| initialValue: control.getRawValue() | ||
| }); | ||
| }); | ||
| value.set = value => { | ||
| options.control().setValue(value); | ||
| }; | ||
| value.update = fn => { | ||
| value.set(fn(value())); | ||
| }; | ||
| return value; | ||
| } | ||
| class CompatStructure extends FieldNodeStructure { | ||
| value; | ||
| keyInParent = () => { | ||
| throw new Error('Compat nodes do not use keyInParent.'); | ||
| }; | ||
| root; | ||
| pathKeys; | ||
| children = signal([], ...(ngDevMode ? [{ | ||
| debugName: "children" | ||
| }] : [])); | ||
| childrenMap = signal(undefined, ...(ngDevMode ? [{ | ||
| debugName: "childrenMap" | ||
| }] : [])); | ||
| parent; | ||
| fieldManager; | ||
| constructor(node, options) { | ||
| super(options.logic); | ||
| this.value = getControlValueSignal(options); | ||
| this.parent = getParentFromOptions(options); | ||
| this.root = this.parent?.structure.root ?? node; | ||
| this.fieldManager = getFieldManagerFromOptions(options); | ||
| this.pathKeys = computed(() => this.parent ? [...this.parent.structure.pathKeys(), this.keyInParent()] : [], ...(ngDevMode ? [{ | ||
| debugName: "pathKeys" | ||
| }] : [])); | ||
| } | ||
| getChild() { | ||
| return undefined; | ||
| } | ||
| } | ||
| class CompatValidationError { | ||
| kind = 'compat'; | ||
| control; | ||
| field; | ||
| context; | ||
| message; | ||
| constructor({ | ||
| context, | ||
| kind, | ||
| control | ||
| }) { | ||
| this.context = context; | ||
| this.kind = kind; | ||
| this.control = control; | ||
| } | ||
| } | ||
| 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; | ||
| } | ||
| const EMPTY_ARRAY_SIGNAL = computed(() => [], ...(ngDevMode ? [{ | ||
| debugName: "EMPTY_ARRAY_SIGNAL" | ||
| }] : [])); | ||
| const TRUE_SIGNAL = computed(() => true, ...(ngDevMode ? [{ | ||
| debugName: "TRUE_SIGNAL" | ||
| }] : [])); | ||
| class CompatValidationState { | ||
| syncValid; | ||
| errors; | ||
| pending; | ||
| invalid; | ||
| valid; | ||
| constructor(options) { | ||
| this.syncValid = getControlStatusSignal(options, c => c.status === 'VALID'); | ||
| this.errors = getControlStatusSignal(options, extractNestedReactiveErrors); | ||
| this.pending = getControlStatusSignal(options, c => c.pending); | ||
| this.valid = getControlStatusSignal(options, c => { | ||
| return c.valid; | ||
| }); | ||
| this.invalid = getControlStatusSignal(options, c => { | ||
| return c.invalid; | ||
| }); | ||
| } | ||
| asyncErrors = EMPTY_ARRAY_SIGNAL; | ||
| errorSummary = EMPTY_ARRAY_SIGNAL; | ||
| rawSyncTreeErrors = EMPTY_ARRAY_SIGNAL; | ||
| syncErrors = EMPTY_ARRAY_SIGNAL; | ||
| rawAsyncErrors = EMPTY_ARRAY_SIGNAL; | ||
| shouldSkipValidation = TRUE_SIGNAL; | ||
| status = computed(() => { | ||
| return calculateValidationSelfStatus(this); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "status" | ||
| }] : [])); | ||
| } | ||
| class CompatFieldAdapter { | ||
| basicAdapter = new BasicFieldAdapter(); | ||
| newRoot(fieldManager, value, pathNode, adapter) { | ||
| if (value() instanceof AbstractControl) { | ||
| return createCompatNode({ | ||
| kind: 'root', | ||
| fieldManager, | ||
| value, | ||
| pathNode, | ||
| logic: pathNode.builder.build(), | ||
| fieldAdapter: adapter | ||
| }); | ||
| } | ||
| return this.basicAdapter.newRoot(fieldManager, value, pathNode, adapter); | ||
| } | ||
| createNodeState(node, options) { | ||
| if (!options.control) { | ||
| return this.basicAdapter.createNodeState(node); | ||
| } | ||
| return new CompatNodeState(node, options); | ||
| } | ||
| createStructure(node, options) { | ||
| if (!options.control) { | ||
| return this.basicAdapter.createStructure(node, options); | ||
| } | ||
| return new CompatStructure(node, options); | ||
| } | ||
| createValidationState(node, options) { | ||
| if (!options.control) { | ||
| return this.basicAdapter.createValidationState(node); | ||
| } | ||
| return new CompatValidationState(options); | ||
| } | ||
| newChild(options) { | ||
| const value = options.parent.value()[options.initialKeyInParent]; | ||
| if (value instanceof AbstractControl) { | ||
| return createCompatNode(options); | ||
| } | ||
| return new FieldNode(options); | ||
| } | ||
| } | ||
| function createCompatNode(options) { | ||
| const control = options.kind === 'root' ? options.value : computed(() => { | ||
| return options.parent.value()[options.initialKeyInParent]; | ||
| }); | ||
| return new CompatFieldNode({ | ||
| ...options, | ||
| control | ||
| }); | ||
| } | ||
| function compatForm(...args) { | ||
| const [model, maybeSchema, maybeOptions] = normalizeFormArgs(args); | ||
| const options = { | ||
| ...maybeOptions, | ||
| adapter: new CompatFieldAdapter() | ||
| }; | ||
| const schema = maybeSchema || (() => {}); | ||
| return form(model, schema, options); | ||
| } | ||
| export { CompatValidationError, compatForm }; | ||
| //# sourceMappingURL=signals-compat.mjs.map |
| {"version":3,"file":"signals-compat.mjs","sources":["../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/forms/signals/compat/src/compat_field_node.ts","../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/forms/signals/compat/src/compat_node_state.ts","../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/forms/signals/compat/src/compat_structure.ts","../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/forms/signals/compat/src/api/compat_validation_error.ts","../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/forms/signals/compat/src/compat_validation_error.ts","../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/forms/signals/compat/src/compat_validation_state.ts","../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/forms/signals/compat/src/compat_field_adapter.ts","../../../../../k8-fastbuild-ST-199a4f3c4e20/bin/packages/forms/signals/compat/src/api/compat_form.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {computed, linkedSignal, runInInjectionContext, Signal, untracked} from '@angular/core';\nimport {toSignal} from '@angular/core/rxjs-interop';\nimport {AbstractControl} from '@angular/forms';\nimport {Observable, ReplaySubject} from 'rxjs';\nimport {map, takeUntil} from 'rxjs/operators';\nimport {FieldNode} from '../../src/field/node';\nimport {getInjectorFromOptions} from '../../src/field/util';\nimport type {CompatFieldNodeOptions} from './compat_structure';\n\n/**\n * Field node with additional control property.\n *\n * Compat node has no children.\n */\nexport class CompatFieldNode extends FieldNode {\n readonly control: Signal<AbstractControl>;\n\n constructor(public readonly options: CompatFieldNodeOptions) {\n super(options);\n this.control = this.options.control;\n }\n}\n\n/**\n * Makes a function which creates a new subject (and unsubscribes/destroys the previous one).\n *\n * This allows us to automatically unsubscribe from status changes of the previous FormControl when we go to subscribe to a new one\n */\nfunction makeCreateDestroySubject() {\n let destroy$ = new ReplaySubject<void>(1);\n return () => {\n if (destroy$) {\n destroy$.next();\n destroy$.complete();\n }\n return (destroy$ = new ReplaySubject<void>(1));\n };\n}\n\n/**\n * Helper function taking options, and a callback which takes options, and a function\n * converting reactive control to appropriate property using toSignal from rxjs compat.\n *\n * This helper keeps all complexity in one place by doing the following things:\n * - Running the callback in injection context\n * - Not tracking the callback, as it creates a new signal.\n * - Reacting to control changes, allowing to swap control dynamically.\n *\n * @param options\n * @param makeSignal\n */\nexport function extractControlPropToSignal<T, R = T>(\n options: CompatFieldNodeOptions,\n makeSignal: (c: AbstractControl<unknown, T>, destroy$: Observable<void>) => Signal<R>,\n): Signal<R> {\n const injector = getInjectorFromOptions(options);\n\n // Creates a subject that could be used in takeUntil.\n const createDestroySubject = makeCreateDestroySubject();\n\n const signalOfControlSignal = linkedSignal({\n source: options.control,\n computation: (control) => {\n return untracked(() => {\n return runInInjectionContext(injector, () => makeSignal(control, createDestroySubject()));\n });\n },\n });\n\n // We have to have computed, because we need to react to both:\n // linked signal changes as well as the inner signal changes.\n return computed(() => signalOfControlSignal()());\n}\n\n/**\n * A helper function, simplifying getting reactive control properties after status changes.\n *\n * Used to extract errors and statuses such as valid, pending.\n *\n * @param options\n * @param getValue\n */\nexport const getControlStatusSignal = <T>(\n options: CompatFieldNodeOptions,\n getValue: (c: AbstractControl<unknown>) => T,\n) => {\n return extractControlPropToSignal<unknown, T>(options, (c, destroy$) =>\n toSignal(\n c.statusChanges.pipe(\n map(() => getValue(c)),\n takeUntil(destroy$),\n ),\n {\n initialValue: getValue(c),\n },\n ),\n );\n};\n\n/**\n * A helper function, simplifying converting convert events to signals.\n *\n * Used to get dirty and touched signals from control.\n *\n * @param options\n * @param getValue A function which takes control and returns required value.\n */\nexport const getControlEventsSignal = <T>(\n options: CompatFieldNodeOptions,\n getValue: (c: AbstractControl) => T,\n) => {\n return extractControlPropToSignal<unknown, T>(options, (c, destroy$) =>\n toSignal(\n c.events.pipe(\n map(() => {\n return getValue(c);\n }),\n takeUntil(destroy$),\n ),\n {\n initialValue: getValue(c),\n },\n ),\n );\n};\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {computed, Signal} from '@angular/core';\nimport {AbstractControl} from '@angular/forms';\nimport {FieldNodeState} from '../../src/field/state';\nimport {CompatFieldNode, getControlEventsSignal, getControlStatusSignal} from './compat_field_node';\nimport {CompatFieldNodeOptions} from './compat_structure';\n\n/**\n * A FieldNodeState class wrapping a FormControl and proxying it's state.\n */\nexport class CompatNodeState extends FieldNodeState {\n override readonly touched: Signal<boolean>;\n override readonly dirty: Signal<boolean>;\n override readonly disabled: Signal<boolean>;\n private readonly control: Signal<AbstractControl>;\n\n constructor(\n readonly compatNode: CompatFieldNode,\n options: CompatFieldNodeOptions,\n ) {\n super(compatNode);\n this.control = options.control;\n this.touched = getControlEventsSignal(options, (c) => c.touched);\n this.dirty = getControlEventsSignal(options, (c) => c.dirty);\n const controlDisabled = getControlStatusSignal(options, (c) => c.disabled);\n\n this.disabled = computed(() => {\n return controlDisabled() || this.disabledReasons().length > 0;\n });\n }\n\n override markAsDirty() {\n this.control().markAsDirty();\n }\n\n override markAsTouched() {\n this.control().markAsTouched();\n }\n\n override markAsPristine() {\n this.control().markAsPristine();\n }\n\n override markAsUntouched() {\n this.control().markAsUntouched();\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {computed, Signal, signal, WritableSignal} from '@angular/core';\nimport {FormFieldManager} from '../../src/field/manager';\nimport {FieldNode, ParentFieldNode} from '../../src/field/node';\nimport {\n ChildFieldNodeOptions,\n FieldNodeOptions,\n FieldNodeStructure,\n RootFieldNodeOptions,\n} from '../../src/field/structure';\n\nimport {toSignal} from '@angular/core/rxjs-interop';\nimport {AbstractControl} from '@angular/forms';\nimport {map, takeUntil} from 'rxjs/operators';\nimport {extractControlPropToSignal} from './compat_field_node';\n\n/**\n * Child Field Node options also exposing control property.\n */\nexport interface CompatChildFieldNodeOptions extends ChildFieldNodeOptions {\n control: Signal<AbstractControl>;\n}\n\n/**\n * Root Field Node options also exposing control property.\n */\nexport interface CompatRootFieldNodeOptions extends RootFieldNodeOptions {\n control: Signal<AbstractControl>;\n}\n\n/**\n * Field Node options also exposing control property.\n */\nexport type CompatFieldNodeOptions = CompatRootFieldNodeOptions | CompatChildFieldNodeOptions;\n\n/**\n * A helper function allowing to get parent if it exists.\n */\nfunction getParentFromOptions(options: FieldNodeOptions) {\n if (options.kind === 'root') {\n return undefined;\n }\n\n return options.parent;\n}\n\n/**\n * A helper function allowing to get fieldManager regardless of the option type.\n */\nfunction getFieldManagerFromOptions(options: FieldNodeOptions) {\n if (options.kind === 'root') {\n return options.fieldManager;\n }\n\n return options.parent.structure.root.structure.fieldManager;\n}\n\n/**\n * A helper function that takes CompatFieldNodeOptions, and produce a writable signal synced to the\n * value of contained AbstractControl.\n *\n * This uses toSignal, which requires an injector.\n *\n * @param options\n */\nfunction getControlValueSignal<T>(options: CompatFieldNodeOptions) {\n const value = extractControlPropToSignal<T>(options, (control, destroy$) => {\n return toSignal(\n control.valueChanges.pipe(\n map(() => control.getRawValue()),\n takeUntil(destroy$),\n ),\n {\n initialValue: control.getRawValue(),\n },\n );\n }) as WritableSignal<T>;\n\n value.set = (value: T) => {\n options.control().setValue(value);\n };\n\n value.update = (fn: (current: T) => T) => {\n value.set(fn(value()));\n };\n\n return value;\n}\n\n/**\n * Compat version of FieldNodeStructure,\n * - It has no children\n * - It wraps FormControl and proxies its value.\n */\nexport class CompatStructure extends FieldNodeStructure {\n override value: WritableSignal<unknown>;\n override keyInParent: Signal<string> = (() => {\n throw new Error('Compat nodes do not use keyInParent.');\n }) as unknown as Signal<string>;\n override root: FieldNode;\n override pathKeys: Signal<readonly string[]>;\n override readonly children = signal([]);\n override readonly childrenMap = signal(undefined);\n override readonly parent: ParentFieldNode | undefined;\n override readonly fieldManager: FormFieldManager;\n\n constructor(node: FieldNode, options: CompatFieldNodeOptions) {\n super(options.logic);\n this.value = getControlValueSignal(options);\n this.parent = getParentFromOptions(options);\n this.root = this.parent?.structure.root ?? node;\n this.fieldManager = getFieldManagerFromOptions(options);\n this.pathKeys = computed(() =>\n this.parent ? [...this.parent.structure.pathKeys(), this.keyInParent()] : [],\n );\n }\n\n override getChild(): FieldNode | undefined {\n return undefined;\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {AbstractControl} from '@angular/forms';\nimport {FieldTree} from '../../../src/api/types';\nimport {ValidationError} from '../../../src/api/validation_errors';\n\n/**\n * An error used for compat errors.\n *\n * @experimental 21.0.0\n * @category interop\n */\nexport class CompatValidationError<T = unknown> implements ValidationError {\n readonly kind: string = 'compat';\n readonly control: AbstractControl;\n readonly field!: FieldTree<unknown>;\n readonly context: T;\n readonly message?: string;\n\n constructor({context, kind, control}: {context: T; kind: string; control: AbstractControl}) {\n this.context = context;\n this.kind = kind;\n this.control = control;\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {AbstractControl, FormArray, FormGroup, ValidationErrors} from '@angular/forms';\nimport {CompatValidationError} from './api/compat_validation_error';\n\n/**\n * Converts reactive form validation error to signal forms CompatValidationError.\n * @param errors\n * @param control\n * @return list of errors.\n */\nexport function reactiveErrorsToSignalErrors(\n errors: ValidationErrors | null,\n control: AbstractControl,\n): CompatValidationError[] {\n if (errors === null) {\n return [];\n }\n\n return Object.entries(errors).map(([kind, context]) => {\n return new CompatValidationError({context, kind, control});\n });\n}\n\nexport function extractNestedReactiveErrors(control: AbstractControl): CompatValidationError[] {\n const errors: CompatValidationError[] = [];\n\n if (control.errors) {\n errors.push(...reactiveErrorsToSignalErrors(control.errors, control));\n }\n\n if (control instanceof FormGroup || control instanceof FormArray) {\n for (const c of Object.values(control.controls)) {\n errors.push(...extractNestedReactiveErrors(c));\n }\n }\n\n return errors;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {computed, Signal} from '@angular/core';\nimport {AbstractControl} from '@angular/forms';\nimport {ValidationError} from '../../src/api/validation_errors';\nimport {calculateValidationSelfStatus, ValidationState} from '../../src/field/validation';\nimport type {CompatValidationError} from './api/compat_validation_error';\nimport {getControlStatusSignal} from './compat_field_node';\nimport {CompatFieldNodeOptions} from './compat_structure';\nimport {extractNestedReactiveErrors} from './compat_validation_error';\n\n// Readonly signal containing an empty array, used for optimization.\nconst EMPTY_ARRAY_SIGNAL = computed(() => []);\nconst TRUE_SIGNAL = computed(() => true);\n\n/**\n * Compat version of a validation state that wraps a FormControl, and proxies it's validation state.\n */\nexport class CompatValidationState implements ValidationState {\n readonly syncValid: Signal<boolean>;\n /**\n * All validation errors for this field.\n */\n readonly errors: Signal<CompatValidationError[]>;\n readonly pending: Signal<boolean>;\n readonly invalid: Signal<boolean>;\n readonly valid: Signal<boolean>;\n\n constructor(options: CompatFieldNodeOptions) {\n this.syncValid = getControlStatusSignal(options, (c: AbstractControl) => c.status === 'VALID');\n this.errors = getControlStatusSignal(options, extractNestedReactiveErrors);\n this.pending = getControlStatusSignal(options, (c) => c.pending);\n\n this.valid = getControlStatusSignal(options, (c) => {\n return c.valid;\n });\n\n this.invalid = getControlStatusSignal(options, (c) => {\n return c.invalid;\n });\n }\n\n asyncErrors: Signal<(ValidationError.WithField | 'pending')[]> = EMPTY_ARRAY_SIGNAL;\n errorSummary: Signal<ValidationError.WithField[]> = EMPTY_ARRAY_SIGNAL;\n\n // Those are irrelevant for compat mode, as it has no children\n rawSyncTreeErrors = EMPTY_ARRAY_SIGNAL;\n syncErrors = EMPTY_ARRAY_SIGNAL;\n rawAsyncErrors = EMPTY_ARRAY_SIGNAL;\n shouldSkipValidation = TRUE_SIGNAL;\n\n /**\n * Computes status based on whether the field is valid/invalid/pending.\n */\n readonly status: Signal<'valid' | 'invalid' | 'unknown'> = computed(() => {\n return calculateValidationSelfStatus(this);\n });\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {computed, Signal, WritableSignal} from '@angular/core';\nimport {AbstractControl} from '@angular/forms';\nimport {BasicFieldAdapter, FieldAdapter} from '../../src/field/field_adapter';\nimport {FormFieldManager} from '../../src/field/manager';\nimport {FieldNode} from '../../src/field/node';\nimport {FieldNodeState} from '../../src/field/state';\nimport {\n ChildFieldNodeOptions,\n FieldNodeOptions,\n FieldNodeStructure,\n} from '../../src/field/structure';\nimport {ValidationState} from '../../src/field/validation';\nimport {FieldPathNode} from '../../src/schema/path_node';\nimport {CompatFieldNode} from './compat_field_node';\nimport {CompatNodeState} from './compat_node_state';\nimport {CompatChildFieldNodeOptions, CompatStructure} from './compat_structure';\nimport {CompatValidationState} from './compat_validation_state';\n\n/**\n * This is a tree-shakable Field adapter that can create a compat node\n * that proxies FormControl state and value to a field.\n */\nexport class CompatFieldAdapter implements FieldAdapter {\n readonly basicAdapter = new BasicFieldAdapter();\n\n /**\n * Creates a regular or compat root node state based on whether the control is present.\n * @param fieldManager\n * @param value\n * @param pathNode\n * @param adapter\n */\n newRoot<TModel>(\n fieldManager: FormFieldManager,\n value: WritableSignal<TModel>,\n pathNode: FieldPathNode,\n adapter: FieldAdapter,\n ): FieldNode {\n if (value() instanceof AbstractControl) {\n return createCompatNode({\n kind: 'root',\n fieldManager,\n value,\n pathNode,\n logic: pathNode.builder.build(),\n fieldAdapter: adapter,\n });\n }\n\n return this.basicAdapter.newRoot<TModel>(fieldManager, value, pathNode, adapter);\n }\n\n /**\n * Creates a regular or compat node state based on whether the control is present.\n * @param node\n * @param options\n */\n createNodeState(node: CompatFieldNode, options: CompatChildFieldNodeOptions): FieldNodeState {\n if (!options.control) {\n return this.basicAdapter.createNodeState(node);\n }\n return new CompatNodeState(node, options);\n }\n\n /**\n * Creates a regular or compat structure based on whether the control is present.\n * @param node\n * @param options\n */\n createStructure(node: CompatFieldNode, options: CompatChildFieldNodeOptions): FieldNodeStructure {\n if (!options.control) {\n return this.basicAdapter.createStructure(node, options);\n }\n return new CompatStructure(node, options);\n }\n\n /**\n * Creates a regular or compat validation state based on whether the control is present.\n * @param node\n * @param options\n */\n createValidationState(\n node: CompatFieldNode,\n options: CompatChildFieldNodeOptions,\n ): ValidationState {\n if (!options.control) {\n return this.basicAdapter.createValidationState(node);\n }\n return new CompatValidationState(options);\n }\n\n /**\n * Creates a regular or compat node based on whether the control is present.\n * @param options\n */\n newChild(options: ChildFieldNodeOptions): FieldNode {\n const value = options.parent.value()[options.initialKeyInParent];\n\n if (value instanceof AbstractControl) {\n return createCompatNode(options);\n }\n\n return new FieldNode(options);\n }\n}\n\n/**\n * Creates a CompatFieldNode from options.\n * @param options\n */\nexport function createCompatNode(options: FieldNodeOptions) {\n const control = (\n options.kind === 'root'\n ? options.value\n : computed(() => {\n return options.parent.value()[options.initialKeyInParent];\n })\n ) as Signal<AbstractControl>;\n\n return new CompatFieldNode({\n ...options,\n control,\n });\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {WritableSignal} from '@angular/core';\nimport {form, FormOptions} from '../../../public_api';\nimport {FieldTree, PathKind, SchemaOrSchemaFn} from '../../../src/api/types';\nimport {normalizeFormArgs} from '../../../src/util/normalize_form_args';\nimport {CompatFieldAdapter} from '../compat_field_adapter';\n\n/**\n * Options that may be specified when creating a compat form.\n *\n * @category interop\n * @experimental 21.0.0\n */\nexport type CompatFormOptions = Omit<FormOptions, 'adapter'>;\n\n/**\n * Creates a compatibility form wrapped around the given model data.\n *\n * `compatForm` is a version of the `form` function that is designed for backwards\n * compatibility with Reactive forms by accepting Reactive controls as a part of the data.\n *\n * @example\n * ```\n * const lastName = new FormControl('lastName');\n *\n * const nameModel = signal({\n * first: '',\n * last: lastName\n * });\n *\n * const nameForm = compatForm(nameModel, (name) => {\n * required(name.first);\n * });\n *\n * nameForm.last().value(); // lastName, not FormControl\n *\n * @param model A writable signal that contains the model data for the form. The resulting field\n * structure will match the shape of the model and any changes to the form data will be written to\n * the model.\n\n * @category interop\n * @experimental 21.0.0\n */\nexport function compatForm<TModel>(model: WritableSignal<TModel>): FieldTree<TModel>;\n\n/**\n * Creates a compatibility form wrapped around the given model data.\n *\n * `compatForm` is a version of the `form` function that is designed for backwards\n * compatibility with Reactive forms by accepting Reactive controls as a part of the data.\n *\n * @example\n * ```\n * const lastName = new FormControl('lastName');\n *\n * const nameModel = signal({\n * first: '',\n * last: lastName\n * });\n *\n * const nameForm = compatForm(nameModel, (name) => {\n * required(name.first);\n * });\n *\n * nameForm.last().value(); // lastName, not FormControl\n *\n * @param model A writable signal that contains the model data for the form. The resulting field\n * structure will match the shape of the model and any changes to the form data will be written to\n * the model.\n * @param schemaOrOptions The second argument can be either\n * 1. A schema or a function used to specify logic for the form (e.g. validation, disabled fields, etc.).\n * When passing a schema, the form options can be passed as a third argument if needed.\n * 2. The form options (excluding adapter, since it's provided).\n *\n * @category interop\n * @experimental 21.0.0\n */\nexport function compatForm<TModel>(\n model: WritableSignal<TModel>,\n schemaOrOptions: SchemaOrSchemaFn<TModel> | CompatFormOptions,\n): FieldTree<TModel>;\n\n/**\n * Creates a compatibility form wrapped around the given model data.\n *\n * `compatForm` is a version of the `form` function that is designed for backwards\n * compatibility with Reactive forms by accepting Reactive controls as a part of the data.\n *\n * @example\n * ```\n * const lastName = new FormControl('lastName');\n *\n * const nameModel = signal({\n * first: '',\n * last: lastName\n * });\n *\n * const nameForm = compatForm(nameModel, (name) => {\n * required(name.first);\n * });\n *\n * nameForm.last().value(); // lastName, not FormControl\n *\n * @param model A writable signal that contains the model data for the form. The resulting field\n * structure will match the shape of the model and any changes to the form data will be written to\n * the model.\n * @param schemaOrOptions A schema or a function used to specify logic for the form (e.g. validation, disabled fields, etc.).\n * When passing a schema, the form options can be passed as a third argument if needed.\n * @param options The form options (excluding adapter, since it's provided).\n *\n * @category interop\n * @experimental 21.0.0\n */\nexport function compatForm<TModel>(\n model: WritableSignal<TModel>,\n schema: SchemaOrSchemaFn<TModel>,\n options: CompatFormOptions,\n): FieldTree<TModel>;\n\nexport function compatForm<TModel>(...args: any[]): FieldTree<TModel> {\n const [model, maybeSchema, maybeOptions] = normalizeFormArgs<TModel>(args);\n\n const options = {...maybeOptions, adapter: new CompatFieldAdapter()};\n const schema = maybeSchema || ((() => {}) as SchemaOrSchemaFn<TModel, PathKind>);\n return form(model, schema, options) as FieldTree<TModel>;\n}\n"],"names":["CompatFieldNode","FieldNode","options","control","constructor","makeCreateDestroySubject","destroy$","ReplaySubject","next","complete","extractControlPropToSignal","makeSignal","injector","getInjectorFromOptions","createDestroySubject","signalOfControlSignal","linkedSignal","ngDevMode","debugName","source","computation","untracked","runInInjectionContext","computed","getControlStatusSignal","getValue","c","toSignal","statusChanges","pipe","map","takeUntil","initialValue","getControlEventsSignal","events","CompatNodeState","FieldNodeState","compatNode","touched","dirty","disabled","controlDisabled","disabledReasons","length","markAsDirty","markAsTouched","markAsPristine","markAsUntouched","getParentFromOptions","kind","undefined","parent","getFieldManagerFromOptions","fieldManager","structure","root","getControlValueSignal","value","valueChanges","getRawValue","set","setValue","update","fn","CompatStructure","FieldNodeStructure","keyInParent","Error","pathKeys","children","signal","childrenMap","node","logic","getChild","CompatValidationError","field","context","message","reactiveErrorsToSignalErrors","errors","Object","entries","extractNestedReactiveErrors","push","FormGroup","FormArray","values","controls","EMPTY_ARRAY_SIGNAL","TRUE_SIGNAL","CompatValidationState","syncValid","pending","invalid","valid","status","asyncErrors","errorSummary","rawSyncTreeErrors","syncErrors","rawAsyncErrors","shouldSkipValidation","calculateValidationSelfStatus","CompatFieldAdapter","basicAdapter","BasicFieldAdapter","newRoot","pathNode","adapter","AbstractControl","createCompatNode","builder","build","fieldAdapter","createNodeState","createStructure","createValidationState","newChild","initialKeyInParent","compatForm","args","model","maybeSchema","maybeOptions","normalizeFormArgs","schema","form"],"mappings":"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"} |
Sorry, the diff of this file is too big to display
| /** | ||
| * @license Angular v21.0.0-rc.3 | ||
| * (c) 2010-2025 Google LLC. https://angular.dev/ | ||
| * License: MIT | ||
| */ | ||
| import { WritableSignal } from '@angular/core'; | ||
| import { FormOptions, FieldTree, SchemaOrSchemaFn, ValidationError } from './_structure-chunk.js'; | ||
| import { AbstractControl } from '@angular/forms'; | ||
| import '@standard-schema/spec'; | ||
| /** | ||
| * Options that may be specified when creating a compat form. | ||
| * | ||
| * @category interop | ||
| * @experimental 21.0.0 | ||
| */ | ||
| type CompatFormOptions = Omit<FormOptions, 'adapter'>; | ||
| /** | ||
| * Creates a compatibility form wrapped around the given model data. | ||
| * | ||
| * `compatForm` is a version of the `form` function that is designed for backwards | ||
| * compatibility with Reactive forms by accepting Reactive controls as a part of the data. | ||
| * | ||
| * @example | ||
| * ``` | ||
| * const lastName = new FormControl('lastName'); | ||
| * | ||
| * const nameModel = signal({ | ||
| * first: '', | ||
| * last: lastName | ||
| * }); | ||
| * | ||
| * const nameForm = compatForm(nameModel, (name) => { | ||
| * required(name.first); | ||
| * }); | ||
| * | ||
| * nameForm.last().value(); // lastName, not FormControl | ||
| * | ||
| * @param model A writable signal that contains the model data for the form. The resulting field | ||
| * structure will match the shape of the model and any changes to the form data will be written to | ||
| * the model. | ||
| * @category interop | ||
| * @experimental 21.0.0 | ||
| */ | ||
| declare function compatForm<TModel>(model: WritableSignal<TModel>): FieldTree<TModel>; | ||
| /** | ||
| * Creates a compatibility form wrapped around the given model data. | ||
| * | ||
| * `compatForm` is a version of the `form` function that is designed for backwards | ||
| * compatibility with Reactive forms by accepting Reactive controls as a part of the data. | ||
| * | ||
| * @example | ||
| * ``` | ||
| * const lastName = new FormControl('lastName'); | ||
| * | ||
| * const nameModel = signal({ | ||
| * first: '', | ||
| * last: lastName | ||
| * }); | ||
| * | ||
| * const nameForm = compatForm(nameModel, (name) => { | ||
| * required(name.first); | ||
| * }); | ||
| * | ||
| * nameForm.last().value(); // lastName, not FormControl | ||
| * | ||
| * @param model A writable signal that contains the model data for the form. The resulting field | ||
| * structure will match the shape of the model and any changes to the form data will be written to | ||
| * the model. | ||
| * @param schemaOrOptions The second argument can be either | ||
| * 1. A schema or a function used to specify logic for the form (e.g. validation, disabled fields, etc.). | ||
| * When passing a schema, the form options can be passed as a third argument if needed. | ||
| * 2. The form options (excluding adapter, since it's provided). | ||
| * | ||
| * @category interop | ||
| * @experimental 21.0.0 | ||
| */ | ||
| declare function compatForm<TModel>(model: WritableSignal<TModel>, schemaOrOptions: SchemaOrSchemaFn<TModel> | CompatFormOptions): FieldTree<TModel>; | ||
| /** | ||
| * Creates a compatibility form wrapped around the given model data. | ||
| * | ||
| * `compatForm` is a version of the `form` function that is designed for backwards | ||
| * compatibility with Reactive forms by accepting Reactive controls as a part of the data. | ||
| * | ||
| * @example | ||
| * ``` | ||
| * const lastName = new FormControl('lastName'); | ||
| * | ||
| * const nameModel = signal({ | ||
| * first: '', | ||
| * last: lastName | ||
| * }); | ||
| * | ||
| * const nameForm = compatForm(nameModel, (name) => { | ||
| * required(name.first); | ||
| * }); | ||
| * | ||
| * nameForm.last().value(); // lastName, not FormControl | ||
| * | ||
| * @param model A writable signal that contains the model data for the form. The resulting field | ||
| * structure will match the shape of the model and any changes to the form data will be written to | ||
| * the model. | ||
| * @param schemaOrOptions A schema or a function used to specify logic for the form (e.g. validation, disabled fields, etc.). | ||
| * When passing a schema, the form options can be passed as a third argument if needed. | ||
| * @param options The form options (excluding adapter, since it's provided). | ||
| * | ||
| * @category interop | ||
| * @experimental 21.0.0 | ||
| */ | ||
| declare function compatForm<TModel>(model: WritableSignal<TModel>, schema: SchemaOrSchemaFn<TModel>, options: CompatFormOptions): FieldTree<TModel>; | ||
| /** | ||
| * An error used for compat errors. | ||
| * | ||
| * @experimental 21.0.0 | ||
| * @category interop | ||
| */ | ||
| declare class CompatValidationError<T = unknown> implements ValidationError { | ||
| readonly kind: string; | ||
| readonly control: AbstractControl; | ||
| readonly field: FieldTree<unknown>; | ||
| readonly context: T; | ||
| readonly message?: string; | ||
| constructor({ context, kind, control }: { | ||
| context: T; | ||
| kind: string; | ||
| control: AbstractControl; | ||
| }); | ||
| } | ||
| export { CompatValidationError, compatForm }; | ||
| export type { CompatFormOptions }; |
+24
-1493
| /** | ||
| * @license Angular v21.0.0-rc.2 | ||
| * @license Angular v21.0.0-rc.3 | ||
| * (c) 2010-2025 Google LLC. https://angular.dev/ | ||
@@ -9,706 +9,8 @@ * License: MIT | ||
| import * as i0 from '@angular/core'; | ||
| import { computed, untracked, InjectionToken, inject, Injector, input, ɵCONTROL as _CONTROL, effect, Directive, runInInjectionContext, linkedSignal, signal, APP_ID, ɵisPromise as _isPromise, resource } from '@angular/core'; | ||
| import { Validators, NG_VALUE_ACCESSOR, NgControl, AbstractControl } from '@angular/forms'; | ||
| import { SIGNAL } from '@angular/core/primitives/signals'; | ||
| import { computed, InjectionToken, inject, Injector, input, ɵCONTROL as _CONTROL, effect, Directive, ɵisPromise as _isPromise, resource } from '@angular/core'; | ||
| import { isArray, assertPathIsCurrent, addDefaultField, FieldPathNode, createMetadataKey, DEBOUNCER, REQUIRED, MAX, MAX_LENGTH, MIN, MIN_LENGTH, PATTERN } from './_structure-chunk.mjs'; | ||
| export { AggregateMetadataKey, MetadataKey, andMetadataKey, apply, applyEach, applyWhen, applyWhenValue, form, listMetadataKey, maxMetadataKey, minMetadataKey, orMetadataKey, reducedMetadataKey, schema, submit } from './_structure-chunk.mjs'; | ||
| import { Validators, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; | ||
| import '@angular/core/primitives/signals'; | ||
| function isArray(value) { | ||
| return Array.isArray(value); | ||
| } | ||
| function isObject(value) { | ||
| return (typeof value === 'object' || typeof value === 'function') && value != null; | ||
| } | ||
| function reduceChildren(node, initialValue, fn, shortCircuit) { | ||
| const childrenMap = node.structure.childrenMap(); | ||
| if (!childrenMap) { | ||
| return initialValue; | ||
| } | ||
| let value = initialValue; | ||
| for (const child of childrenMap.values()) { | ||
| if (shortCircuit?.(value)) { | ||
| break; | ||
| } | ||
| value = fn(child, value); | ||
| } | ||
| return value; | ||
| } | ||
| function shortCircuitFalse(value) { | ||
| return !value; | ||
| } | ||
| function shortCircuitTrue(value) { | ||
| return value; | ||
| } | ||
| 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" | ||
| }] : [])); | ||
| syncErrors = computed(() => { | ||
| if (this.shouldSkipValidation()) { | ||
| return []; | ||
| } | ||
| return [...this.node.logicNode.logic.syncErrors.compute(this.node.context), ...this.syncTreeErrors(), ...normalizeErrors(this.node.submitState.serverErrors())]; | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "syncErrors" | ||
| }] : [])); | ||
| syncValid = computed(() => { | ||
| if (this.shouldSkipValidation()) { | ||
| return true; | ||
| } | ||
| return reduceChildren(this.node, this.syncErrors().length === 0, (child, value) => value && child.validationState.syncValid(), shortCircuitFalse); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "syncValid" | ||
| }] : [])); | ||
| syncTreeErrors = computed(() => this.rawSyncTreeErrors().filter(err => err.field === this.node.fieldProxy), ...(ngDevMode ? [{ | ||
| debugName: "syncTreeErrors" | ||
| }] : [])); | ||
| 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" | ||
| }] : [])); | ||
| asyncErrors = computed(() => { | ||
| if (this.shouldSkipValidation()) { | ||
| return []; | ||
| } | ||
| return this.rawAsyncErrors().filter(err => err === 'pending' || err.field === this.node.fieldProxy); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "asyncErrors" | ||
| }] : [])); | ||
| errors = computed(() => [...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], ...(ngDevMode ? [{ | ||
| debugName: "errors" | ||
| }] : [])); | ||
| errorSummary = computed(() => reduceChildren(this.node, this.errors(), (child, result) => [...result, ...child.errorSummary()]), ...(ngDevMode ? [{ | ||
| debugName: "errorSummary" | ||
| }] : [])); | ||
| pending = computed(() => reduceChildren(this.node, this.asyncErrors().includes('pending'), (child, value) => value || child.validationState.asyncErrors().includes('pending')), ...(ngDevMode ? [{ | ||
| debugName: "pending" | ||
| }] : [])); | ||
| status = computed(() => { | ||
| if (this.shouldSkipValidation()) { | ||
| return 'valid'; | ||
| } | ||
| let ownStatus = calculateValidationSelfStatus(this); | ||
| return reduceChildren(this.node, 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(), ...(ngDevMode ? [{ | ||
| debugName: "shouldSkipValidation" | ||
| }] : [])); | ||
| } | ||
| function normalizeErrors(error) { | ||
| if (error === undefined) { | ||
| return []; | ||
| } | ||
| if (isArray(error)) { | ||
| return error; | ||
| } | ||
| return [error]; | ||
| } | ||
| function addDefaultField(errors, field) { | ||
| if (isArray(errors)) { | ||
| for (const error of errors) { | ||
| error.field ??= field; | ||
| } | ||
| } else if (errors) { | ||
| errors.field ??= field; | ||
| } | ||
| return errors; | ||
| } | ||
| let boundPathDepth = 0; | ||
| function getBoundPathDepth() { | ||
| return boundPathDepth; | ||
| } | ||
| function setBoundPathDepthForResolution(fn, depth) { | ||
| return (...args) => { | ||
| try { | ||
| boundPathDepth = depth; | ||
| return fn(...args); | ||
| } finally { | ||
| boundPathDepth = 0; | ||
| } | ||
| }; | ||
| } | ||
| 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); | ||
| } | ||
| } | ||
| 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 AggregateMetadataMergeLogic extends AbstractLogic { | ||
| key; | ||
| get defaultValue() { | ||
| return this.key.getInitial(); | ||
| } | ||
| constructor(predicates, key) { | ||
| super(predicates); | ||
| this.key = key; | ||
| } | ||
| compute(ctx) { | ||
| if (this.fns.length === 0) { | ||
| return this.key.getInitial(); | ||
| } | ||
| let acc = this.key.getInitial(); | ||
| for (let i = 0; i < this.fns.length; i++) { | ||
| const item = this.fns[i](ctx); | ||
| if (item !== IGNORED) { | ||
| acc = this.key.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; | ||
| aggregateMetadataKeys = new Map(); | ||
| metadataFactories = 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); | ||
| } | ||
| hasAggregateMetadata(key) { | ||
| return this.aggregateMetadataKeys.has(key); | ||
| } | ||
| getAggregateMetadataEntries() { | ||
| return this.aggregateMetadataKeys.entries(); | ||
| } | ||
| getMetadataFactoryEntries() { | ||
| return this.metadataFactories.entries(); | ||
| } | ||
| getAggregateMetadata(key) { | ||
| if (!this.aggregateMetadataKeys.has(key)) { | ||
| this.aggregateMetadataKeys.set(key, new AggregateMetadataMergeLogic(this.predicates, key)); | ||
| } | ||
| return this.aggregateMetadataKeys.get(key); | ||
| } | ||
| addMetadataFactory(key, factory) { | ||
| if (this.metadataFactories.has(key)) { | ||
| throw new Error(`Can't define value twice for the same MetadataKey`); | ||
| } | ||
| this.metadataFactories.set(key, factory); | ||
| } | ||
| 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, metadataLogic] of other.getAggregateMetadataEntries()) { | ||
| this.getAggregateMetadata(key).mergeIn(metadataLogic); | ||
| } | ||
| for (const [key, metadataFactory] of other.getMetadataFactoryEntries()) { | ||
| this.addMetadataFactory(key, metadataFactory); | ||
| } | ||
| } | ||
| } | ||
| 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); | ||
| } | ||
| addAggregateMetadataRule(key, logic) { | ||
| this.getCurrent().addAggregateMetadataRule(key, logic); | ||
| } | ||
| addMetadataFactory(key, factory) { | ||
| this.getCurrent().addMetadataFactory(key, factory); | ||
| } | ||
| 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)); | ||
| } | ||
| 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)); | ||
| } | ||
| addAggregateMetadataRule(key, logic) { | ||
| this.logic.getAggregateMetadata(key).push(setBoundPathDepthForResolution(logic, this.depth)); | ||
| } | ||
| addMetadataFactory(key, factory) { | ||
| this.logic.addMetadataFactory(key, setBoundPathDepthForResolution(factory, 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; | ||
| } | ||
| } | ||
| 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) { | ||
| return this.builder?.hasLogic(builder) ?? 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)); | ||
| } | ||
| } | ||
| 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 Error('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 Error('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 Error(`A FieldPath can only be used directly within the Schema that owns it,` + ` **not** outside of it or within a sub-schema.`); | ||
| } | ||
| } | ||
| class MetadataKey { | ||
| brand; | ||
| constructor() {} | ||
| } | ||
| function createMetadataKey() { | ||
| return new MetadataKey(); | ||
| } | ||
| class AggregateMetadataKey { | ||
| reduce; | ||
| getInitial; | ||
| brand; | ||
| constructor(reduce, getInitial) { | ||
| this.reduce = reduce; | ||
| this.getInitial = getInitial; | ||
| } | ||
| } | ||
| function reducedMetadataKey(reduce, getInitial) { | ||
| return new AggregateMetadataKey(reduce, getInitial); | ||
| } | ||
| function listMetadataKey() { | ||
| return reducedMetadataKey((acc, item) => item === undefined ? acc : [...acc, item], () => []); | ||
| } | ||
| function minMetadataKey() { | ||
| return reducedMetadataKey((prev, next) => { | ||
| if (prev === undefined) { | ||
| return next; | ||
| } | ||
| if (next === undefined) { | ||
| return prev; | ||
| } | ||
| return Math.min(prev, next); | ||
| }, () => undefined); | ||
| } | ||
| function maxMetadataKey() { | ||
| return reducedMetadataKey((prev, next) => { | ||
| if (prev === undefined) { | ||
| return next; | ||
| } | ||
| if (next === undefined) { | ||
| return prev; | ||
| } | ||
| return Math.max(prev, next); | ||
| }, () => undefined); | ||
| } | ||
| function orMetadataKey() { | ||
| return reducedMetadataKey((prev, next) => prev || next, () => false); | ||
| } | ||
| function andMetadataKey() { | ||
| return reducedMetadataKey((prev, next) => prev && next, () => true); | ||
| } | ||
| const REQUIRED = orMetadataKey(); | ||
| const MIN = maxMetadataKey(); | ||
| const MAX = minMetadataKey(); | ||
| const MIN_LENGTH = maxMetadataKey(); | ||
| const MAX_LENGTH = minMetadataKey(); | ||
| const PATTERN = listMetadataKey(); | ||
| function requiredError(options) { | ||
@@ -961,4 +263,2 @@ return new RequiredValidationError(options); | ||
| const DEBOUNCER = reducedMetadataKey((_, item) => item, () => undefined); | ||
| function debounce(path, durationOrDebouncer) { | ||
@@ -971,4 +271,7 @@ assertPathIsCurrent(path); | ||
| function debounceForDuration(durationInMilliseconds) { | ||
| return () => { | ||
| return new Promise(resolve => setTimeout(resolve, durationInMilliseconds)); | ||
| return (_context, abortSignal) => { | ||
| return new Promise(resolve => { | ||
| const timeoutId = setTimeout(resolve, durationInMilliseconds); | ||
| abortSignal.addEventListener('abort', () => clearTimeout(timeoutId)); | ||
| }); | ||
| }; | ||
@@ -1084,3 +387,3 @@ } | ||
| minVersion: "12.0.0", | ||
| version: "21.0.0-rc.2", | ||
| version: "21.0.0-rc.3", | ||
| ngImport: i0, | ||
@@ -1093,3 +396,3 @@ type: Field, | ||
| minVersion: "17.1.0", | ||
| version: "21.0.0-rc.2", | ||
| version: "21.0.0-rc.3", | ||
| type: Field, | ||
@@ -1119,3 +422,3 @@ isStandalone: true, | ||
| minVersion: "12.0.0", | ||
| version: "21.0.0-rc.2", | ||
| version: "21.0.0-rc.3", | ||
| ngImport: i0, | ||
@@ -1148,780 +451,2 @@ type: Field, | ||
| class FieldNodeContext { | ||
| node; | ||
| cache = new WeakMap(); | ||
| constructor(node) { | ||
| this.node = node; | ||
| } | ||
| 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 Error('Path is not part of this field tree.'); | ||
| } | ||
| } | ||
| for (let key of targetPathNode.keys) { | ||
| field = field.structure.getChild(key); | ||
| if (field === undefined) { | ||
| throw new Error(`Cannot resolve path .${targetPathNode.keys.join('.')} relative to field ${['<root>', ...this.node.structure.pathKeys()].join('.')}.`); | ||
| } | ||
| } | ||
| return field.fieldProxy; | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "resolver" | ||
| }] : [])); | ||
| this.cache.set(target, resolver); | ||
| } | ||
| return this.cache.get(target)(); | ||
| } | ||
| get field() { | ||
| 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 Error(`RuntimeError: cannot access index, parent field is not an array`); | ||
| } | ||
| return Number(key); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "index" | ||
| }] : [])); | ||
| fieldTreeOf = p => this.resolve(p); | ||
| stateOf = p => this.resolve(p)(); | ||
| valueOf = p => { | ||
| const result = this.resolve(p)().value(); | ||
| if (result instanceof AbstractControl) { | ||
| throw new Error(`Tried to read an 'AbstractControl' value form a 'form()'. Did you mean to use 'compatForm()' instead?`); | ||
| } | ||
| return result; | ||
| }; | ||
| } | ||
| class FieldMetadataState { | ||
| node; | ||
| metadata = new Map(); | ||
| constructor(node) { | ||
| this.node = node; | ||
| untracked(() => runInInjectionContext(this.node.structure.injector, () => { | ||
| for (const [key, factory] of this.node.logicNode.logic.getMetadataFactoryEntries()) { | ||
| this.metadata.set(key, factory(this.node.context)); | ||
| } | ||
| })); | ||
| } | ||
| get(key) { | ||
| if (key instanceof MetadataKey) { | ||
| return this.metadata.get(key); | ||
| } | ||
| if (!this.metadata.has(key)) { | ||
| const logic = this.node.logicNode.logic.getAggregateMetadata(key); | ||
| const result = computed(() => logic.compute(this.node.context), ...(ngDevMode ? [{ | ||
| debugName: "result" | ||
| }] : [])); | ||
| this.metadata.set(key, result); | ||
| } | ||
| return this.metadata.get(key); | ||
| } | ||
| has(key) { | ||
| if (key instanceof AggregateMetadataKey) { | ||
| return this.node.logicNode.logic.hasAggregateMetadata(key); | ||
| } else { | ||
| return this.metadata.has(key); | ||
| } | ||
| } | ||
| } | ||
| const FIELD_PROXY_HANDLER = { | ||
| get(getTgt, p, receiver) { | ||
| const tgt = getTgt(); | ||
| const child = tgt.structure.getChild(p); | ||
| if (child !== undefined) { | ||
| return child.fieldProxy; | ||
| } | ||
| const value = untracked(tgt.value); | ||
| if (isArray(value)) { | ||
| if (p === 'length') { | ||
| return tgt.value().length; | ||
| } | ||
| if (p === Symbol.iterator) { | ||
| return Array.prototype[p]; | ||
| } | ||
| } | ||
| if (isObject(value)) { | ||
| if (p === 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 => { | ||
| 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 | ||
| }; | ||
| } | ||
| } | ||
| class FieldNodeStructure { | ||
| logic; | ||
| identitySymbol = Symbol(); | ||
| _injector = undefined; | ||
| get injector() { | ||
| this._injector ??= Injector.create({ | ||
| providers: [], | ||
| parent: this.fieldManager.injector | ||
| }); | ||
| return this._injector; | ||
| } | ||
| constructor(logic) { | ||
| this.logic = logic; | ||
| } | ||
| children() { | ||
| return this.childrenMap()?.values() ?? []; | ||
| } | ||
| getChild(key) { | ||
| const map = this.childrenMap(); | ||
| const value = this.value(); | ||
| if (!map || !isObject(value)) { | ||
| return undefined; | ||
| } | ||
| if (isArray(value)) { | ||
| const childValue = value[key]; | ||
| if (isObject(childValue) && childValue.hasOwnProperty(this.identitySymbol)) { | ||
| key = childValue[this.identitySymbol]; | ||
| } | ||
| } | ||
| return map.get(typeof key === 'number' ? key.toString() : key); | ||
| } | ||
| destroy() { | ||
| this.injector.destroy(); | ||
| } | ||
| } | ||
| class RootFieldNodeStructure extends FieldNodeStructure { | ||
| node; | ||
| fieldManager; | ||
| value; | ||
| get parent() { | ||
| return undefined; | ||
| } | ||
| get root() { | ||
| return this.node; | ||
| } | ||
| get pathKeys() { | ||
| return ROOT_PATH_KEYS; | ||
| } | ||
| get keyInParent() { | ||
| return ROOT_KEY_IN_PARENT; | ||
| } | ||
| childrenMap; | ||
| constructor(node, pathNode, logic, fieldManager, value, adapter, createChildNode) { | ||
| super(logic); | ||
| this.node = node; | ||
| this.fieldManager = fieldManager; | ||
| this.value = value; | ||
| this.childrenMap = makeChildrenMapSignal(node, value, this.identitySymbol, pathNode, logic, adapter, createChildNode); | ||
| } | ||
| } | ||
| class ChildFieldNodeStructure extends FieldNodeStructure { | ||
| parent; | ||
| root; | ||
| pathKeys; | ||
| keyInParent; | ||
| value; | ||
| childrenMap; | ||
| get fieldManager() { | ||
| return this.root.structure.fieldManager; | ||
| } | ||
| constructor(node, pathNode, logic, parent, identityInParent, initialKeyInParent, adapter, createChildNode) { | ||
| super(logic); | ||
| this.parent = parent; | ||
| this.root = this.parent.structure.root; | ||
| this.pathKeys = computed(() => [...parent.structure.pathKeys(), this.keyInParent()], ...(ngDevMode ? [{ | ||
| debugName: "pathKeys" | ||
| }] : [])); | ||
| if (identityInParent === undefined) { | ||
| const key = initialKeyInParent; | ||
| this.keyInParent = computed(() => { | ||
| if (parent.structure.childrenMap()?.get(key) !== node) { | ||
| throw new Error(`RuntimeError: orphan field, looking for property '${key}' of ${getDebugName(parent)}`); | ||
| } | ||
| return key; | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "keyInParent" | ||
| }] : [])); | ||
| } else { | ||
| let lastKnownKey = initialKeyInParent; | ||
| this.keyInParent = computed(() => { | ||
| const parentValue = parent.structure.value(); | ||
| if (!isArray(parentValue)) { | ||
| throw new Error(`RuntimeError: orphan field, expected ${getDebugName(parent)} to be an array`); | ||
| } | ||
| const data = parentValue[lastKnownKey]; | ||
| if (isObject(data) && data.hasOwnProperty(parent.structure.identitySymbol) && data[parent.structure.identitySymbol] === identityInParent) { | ||
| return lastKnownKey; | ||
| } | ||
| for (let i = 0; i < parentValue.length; i++) { | ||
| const data = parentValue[i]; | ||
| if (isObject(data) && data.hasOwnProperty(parent.structure.identitySymbol) && data[parent.structure.identitySymbol] === identityInParent) { | ||
| return lastKnownKey = i.toString(); | ||
| } | ||
| } | ||
| throw new Error(`RuntimeError: orphan field, can't find element in array ${getDebugName(parent)}`); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "keyInParent" | ||
| }] : [])); | ||
| } | ||
| this.value = deepSignal(this.parent.structure.value, this.keyInParent); | ||
| this.childrenMap = makeChildrenMapSignal(node, this.value, this.identitySymbol, pathNode, logic, adapter, createChildNode); | ||
| 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 Error(`RuntimeError: the top-level field in the form has no parent`); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "ROOT_KEY_IN_PARENT" | ||
| }] : [])); | ||
| function makeChildrenMapSignal(node, valueSignal, identitySymbol, pathNode, logic, adapter, createChildNode) { | ||
| return linkedSignal({ | ||
| source: valueSignal, | ||
| computation: (value, previous) => { | ||
| let childrenMap = previous?.value; | ||
| if (!isObject(value)) { | ||
| return undefined; | ||
| } | ||
| const isValueArray = isArray(value); | ||
| if (childrenMap !== undefined) { | ||
| let oldKeys = undefined; | ||
| if (isValueArray) { | ||
| oldKeys = new Set(childrenMap.keys()); | ||
| for (let i = 0; i < value.length; i++) { | ||
| const childValue = value[i]; | ||
| if (isObject(childValue) && childValue.hasOwnProperty(identitySymbol)) { | ||
| oldKeys.delete(childValue[identitySymbol]); | ||
| } else { | ||
| oldKeys.delete(i.toString()); | ||
| } | ||
| } | ||
| for (const key of oldKeys) { | ||
| childrenMap.delete(key); | ||
| } | ||
| } else { | ||
| for (let key of childrenMap.keys()) { | ||
| if (!value.hasOwnProperty(key)) { | ||
| childrenMap.delete(key); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| for (let key of Object.keys(value)) { | ||
| let trackingId = undefined; | ||
| const childValue = value[key]; | ||
| if (childValue === undefined) { | ||
| childrenMap?.delete(key); | ||
| continue; | ||
| } | ||
| if (isValueArray && isObject(childValue) && !isArray(childValue)) { | ||
| trackingId = childValue[identitySymbol] ??= Symbol(ngDevMode ? `id:${globalId++}` : ''); | ||
| } | ||
| const identity = trackingId ?? key; | ||
| if (childrenMap?.has(identity)) { | ||
| continue; | ||
| } | ||
| let childPath; | ||
| let childLogic; | ||
| if (isValueArray) { | ||
| childPath = pathNode.getChild(DYNAMIC); | ||
| childLogic = logic.getChild(DYNAMIC); | ||
| } else { | ||
| childPath = pathNode.getChild(key); | ||
| childLogic = logic.getChild(key); | ||
| } | ||
| childrenMap ??= new Map(); | ||
| childrenMap.set(identity, createChildNode({ | ||
| kind: 'child', | ||
| parent: node, | ||
| pathNode: childPath, | ||
| logic: childLogic, | ||
| initialKeyInParent: key, | ||
| identityInParent: trackingId, | ||
| fieldAdapter: adapter | ||
| })); | ||
| } | ||
| return childrenMap; | ||
| }, | ||
| equal: () => false | ||
| }); | ||
| } | ||
| function getDebugName(node) { | ||
| return `<root>.${node.structure.pathKeys().join('.')}`; | ||
| } | ||
| class FieldSubmitState { | ||
| node; | ||
| selfSubmitting = signal(false, ...(ngDevMode ? [{ | ||
| debugName: "selfSubmitting" | ||
| }] : [])); | ||
| serverErrors; | ||
| constructor(node) { | ||
| this.node = node; | ||
| this.serverErrors = linkedSignal(...(ngDevMode ? [{ | ||
| debugName: "serverErrors", | ||
| source: this.node.structure.value, | ||
| computation: () => [] | ||
| }] : [{ | ||
| 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; | ||
| _context = undefined; | ||
| get context() { | ||
| return this._context ??= new FieldNodeContext(this); | ||
| } | ||
| fieldProxy = new Proxy(() => this, FIELD_PROXY_HANDLER); | ||
| constructor(options) { | ||
| 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); | ||
| } | ||
| pendingSync = linkedSignal(...(ngDevMode ? [{ | ||
| debugName: "pendingSync", | ||
| source: () => this.value(), | ||
| computation: () => undefined | ||
| }] : [{ | ||
| source: () => this.value(), | ||
| computation: () => undefined | ||
| }])); | ||
| get logicNode() { | ||
| return this.structure.logic; | ||
| } | ||
| get value() { | ||
| return this.structure.value; | ||
| } | ||
| _controlValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{ | ||
| debugName: "_controlValue" | ||
| }] : [])); | ||
| get controlValue() { | ||
| return this._controlValue.asReadonly(); | ||
| } | ||
| get keyInParent() { | ||
| return this.structure.keyInParent; | ||
| } | ||
| get errors() { | ||
| return this.validationState.errors; | ||
| } | ||
| 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 fieldBindings() { | ||
| return this.nodeState.fieldBindings; | ||
| } | ||
| get submitting() { | ||
| return this.submitState.submitting; | ||
| } | ||
| get name() { | ||
| return this.nodeState.name; | ||
| } | ||
| metadataOrUndefined(key) { | ||
| return this.hasMetadata(key) ? this.metadata(key) : undefined; | ||
| } | ||
| get max() { | ||
| return this.metadataOrUndefined(MAX); | ||
| } | ||
| get maxLength() { | ||
| return this.metadataOrUndefined(MAX_LENGTH); | ||
| } | ||
| get min() { | ||
| return this.metadataOrUndefined(MIN); | ||
| } | ||
| get minLength() { | ||
| return this.metadataOrUndefined(MIN_LENGTH); | ||
| } | ||
| get pattern() { | ||
| return this.metadataOrUndefined(PATTERN) ?? EMPTY; | ||
| } | ||
| get required() { | ||
| return this.metadataOrUndefined(REQUIRED) ?? FALSE; | ||
| } | ||
| metadata(key) { | ||
| return this.metadataState.get(key); | ||
| } | ||
| hasMetadata(key) { | ||
| return this.metadataState.has(key); | ||
| } | ||
| markAsTouched() { | ||
| this.nodeState.markAsTouched(); | ||
| this.sync(); | ||
| } | ||
| markAsDirty() { | ||
| this.nodeState.markAsDirty(); | ||
| } | ||
| reset() { | ||
| this.nodeState.markAsUntouched(); | ||
| this.nodeState.markAsPristine(); | ||
| for (const child of this.structure.children()) { | ||
| child.reset(); | ||
| } | ||
| } | ||
| setControlValue(newValue) { | ||
| this._controlValue.set(newValue); | ||
| this.markAsDirty(); | ||
| this.debounceSync(); | ||
| } | ||
| sync() { | ||
| this.value.set(this.controlValue()); | ||
| this.pendingSync.set(undefined); | ||
| } | ||
| debounceSync() { | ||
| const promise = this.nodeState.debouncer(); | ||
| if (promise) { | ||
| promise.then(() => { | ||
| if (promise === this.pendingSync()) { | ||
| this.sync(); | ||
| } | ||
| }); | ||
| this.pendingSync.set(promise); | ||
| } else { | ||
| this.sync(); | ||
| } | ||
| } | ||
| static newRoot(fieldManager, value, pathNode, adapter) { | ||
| return adapter.newRoot(fieldManager, value, pathNode, adapter); | ||
| } | ||
| static newChild(options) { | ||
| return options.fieldAdapter.newChild(options); | ||
| } | ||
| createStructure(options) { | ||
| return options.kind === 'root' ? new RootFieldNodeStructure(this, options.pathNode, options.logic, options.fieldManager, options.value, options.fieldAdapter, FieldNode.newChild) : new ChildFieldNodeStructure(this, options.pathNode, options.logic, options.parent, options.identityInParent, options.initialKeyInParent, options.fieldAdapter, FieldNode.newChild); | ||
| } | ||
| } | ||
| const EMPTY = computed(() => [], ...(ngDevMode ? [{ | ||
| debugName: "EMPTY" | ||
| }] : [])); | ||
| const FALSE = computed(() => false, ...(ngDevMode ? [{ | ||
| debugName: "FALSE" | ||
| }] : [])); | ||
| 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); | ||
| } | ||
| fieldBindings = signal([], ...(ngDevMode ? [{ | ||
| debugName: "fieldBindings" | ||
| }] : [])); | ||
| constructor(node) { | ||
| this.node = node; | ||
| } | ||
| dirty = computed(() => { | ||
| const selfDirtyValue = this.selfDirty() && !this.isNonInteractive(); | ||
| return reduceChildren(this.node, selfDirtyValue, (child, value) => value || child.nodeState.dirty(), shortCircuitTrue); | ||
| }, ...(ngDevMode ? [{ | ||
| debugName: "dirty" | ||
| }] : [])); | ||
| touched = computed(() => { | ||
| const selfTouchedValue = this.selfTouched() && !this.isNonInteractive(); | ||
| return reduceChildren(this.node, 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" | ||
| }] : [])); | ||
| 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() { | ||
| if (this.node.logicNode.logic.hasAggregateMetadata(DEBOUNCER)) { | ||
| const debouncerLogic = this.node.logicNode.logic.getAggregateMetadata(DEBOUNCER); | ||
| const debouncer = debouncerLogic.compute(this.node.context); | ||
| if (debouncer) { | ||
| return debouncer(this.node.context); | ||
| } | ||
| } | ||
| return this.node.structure.parent?.nodeState.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; | ||
| constructor(injector, rootName) { | ||
| this.injector = injector; | ||
| this.rootName = rootName ?? `${this.injector.get(APP_ID)}.form${nextFormId++}`; | ||
| } | ||
| 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; | ||
| 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); | ||
| const adapter = options?.adapter ?? new BasicFieldAdapter(); | ||
| const fieldRoot = FieldNode.newRoot(fieldManager, model, pathNode, adapter); | ||
| fieldManager.createFieldManagementEffect(fieldRoot.structure); | ||
| return fieldRoot.fieldProxy; | ||
| } | ||
| 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, action) { | ||
| const node = form(); | ||
| markAllAsTouched(node); | ||
| if (node.invalid()) { | ||
| return; | ||
| } | ||
| node.submitState.selfSubmitting.set(true); | ||
| try { | ||
| const errors = await action(form); | ||
| errors && setServerErrors(node, errors); | ||
| } finally { | ||
| node.submitState.selfSubmitting.set(false); | ||
| } | ||
| } | ||
| function setServerErrors(submittedField, errors) { | ||
| if (!isArray(errors)) { | ||
| errors = [errors]; | ||
| } | ||
| const errorsByField = new Map(); | ||
| for (const error of errors) { | ||
| const errorWithField = addDefaultField(error, submittedField.fieldProxy); | ||
| const field = errorWithField.field(); | ||
| let fieldErrors = errorsByField.get(field); | ||
| if (!fieldErrors) { | ||
| fieldErrors = []; | ||
| errorsByField.set(field, fieldErrors); | ||
| } | ||
| fieldErrors.push(errorWithField); | ||
| } | ||
| for (const [field, fieldErrors] of errorsByField) { | ||
| field.submitState.serverErrors.set(fieldErrors); | ||
| } | ||
| } | ||
| function schema(fn) { | ||
| return SchemaImpl.create(fn); | ||
| } | ||
| function markAllAsTouched(node) { | ||
| node.markAsTouched(); | ||
| for (const child of node.structure.children()) { | ||
| markAllAsTouched(child); | ||
| } | ||
| } | ||
| const EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; | ||
@@ -1959,3 +484,5 @@ function email(path, config) { | ||
| } | ||
| if (ctx.value() > max) { | ||
| const value = ctx.value(); | ||
| const numValue = !value && value !== 0 ? NaN : Number(value); | ||
| if (numValue > max) { | ||
| if (config?.error) { | ||
@@ -2012,3 +539,5 @@ return getOption(config.error, ctx); | ||
| } | ||
| if (ctx.value() < min) { | ||
| const value = ctx.value(); | ||
| const numValue = !value && value !== 0 ? NaN : Number(value); | ||
| if (numValue < min) { | ||
| if (config?.error) { | ||
@@ -2142,6 +671,8 @@ return getOption(config.error, ctx); | ||
| } | ||
| return addDefaultField(standardSchemaError(issue), target); | ||
| return addDefaultField(standardSchemaError(issue, { | ||
| message: issue.message | ||
| }), target); | ||
| } | ||
| export { AggregateMetadataKey, CustomValidationError, EmailValidationError, FIELD, Field, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MetadataKey, MinLengthValidationError, MinValidationError, NgValidationError, PATTERN, PatternValidationError, REQUIRED, RequiredValidationError, StandardSchemaValidationError, aggregateMetadata, andMetadataKey, apply, applyEach, applyWhen, applyWhenValue, createMetadataKey, customError, debounce, disabled, email, emailError, form, hidden, listMetadataKey, max, maxError, maxLength, maxLengthError, maxMetadataKey, metadata, min, minError, minLength, minLengthError, minMetadataKey, orMetadataKey, pattern, patternError, readonly, reducedMetadataKey, required, requiredError, schema, standardSchemaError, submit, validate, validateAsync, validateHttp, validateStandardSchema, validateTree }; | ||
| export { CustomValidationError, EmailValidationError, FIELD, Field, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MinLengthValidationError, MinValidationError, NgValidationError, PATTERN, PatternValidationError, REQUIRED, RequiredValidationError, StandardSchemaValidationError, aggregateMetadata, createMetadataKey, customError, debounce, disabled, email, emailError, hidden, max, maxError, maxLength, maxLengthError, metadata, min, minError, minLength, minLengthError, pattern, patternError, readonly, required, requiredError, standardSchemaError, validate, validateAsync, validateHttp, validateStandardSchema, validateTree }; | ||
| //# sourceMappingURL=signals.mjs.map |
+8
-4
| { | ||
| "name": "@angular/forms", | ||
| "version": "21.0.0-rc.2", | ||
| "version": "21.0.0-rc.3", | ||
| "description": "Angular - directives and services for creating forms", | ||
@@ -14,5 +14,5 @@ "author": "angular", | ||
| "peerDependencies": { | ||
| "@angular/core": "21.0.0-rc.2", | ||
| "@angular/common": "21.0.0-rc.2", | ||
| "@angular/platform-browser": "21.0.0-rc.2", | ||
| "@angular/core": "21.0.0-rc.3", | ||
| "@angular/common": "21.0.0-rc.3", | ||
| "@angular/platform-browser": "21.0.0-rc.3", | ||
| "@standard-schema/spec": "^1.0.0", | ||
@@ -61,4 +61,8 @@ "rxjs": "^6.5.3 || ^7.4.0" | ||
| "default": "./fesm2022/signals.mjs" | ||
| }, | ||
| "./signals/compat": { | ||
| "types": "./types/signals-compat.d.ts", | ||
| "default": "./fesm2022/signals-compat.mjs" | ||
| } | ||
| } | ||
| } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
1409377
4.07%15
66.67%16044
3.05%80
-5.88%