mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1602 lines
46 KiB
1602 lines
46 KiB
/**
|
|
* @license Angular v21.1.1
|
|
* (c) 2010-2026 Google LLC. https://angular.dev/
|
|
* License: MIT
|
|
*/
|
|
|
|
import { untracked, ɵRuntimeError as _RuntimeError, computed, runInInjectionContext, Injector, linkedSignal, signal, APP_ID, effect, inject } from '@angular/core';
|
|
import { AbstractControl } from '@angular/forms';
|
|
import { SIGNAL } from '@angular/core/primitives/signals';
|
|
|
|
let boundPathDepth = 0;
|
|
function getBoundPathDepth() {
|
|
return boundPathDepth;
|
|
}
|
|
function setBoundPathDepthForResolution(fn, depth) {
|
|
return (...args) => {
|
|
try {
|
|
boundPathDepth = depth;
|
|
return fn(...args);
|
|
} finally {
|
|
boundPathDepth = 0;
|
|
}
|
|
};
|
|
}
|
|
|
|
function shortCircuitFalse(value) {
|
|
return !value;
|
|
}
|
|
function shortCircuitTrue(value) {
|
|
return value;
|
|
}
|
|
function getInjectorFromOptions(options) {
|
|
if (options.kind === 'root') {
|
|
return options.fieldManager.injector;
|
|
}
|
|
return options.parent.structure.root.structure.injector;
|
|
}
|
|
|
|
function isArray(value) {
|
|
return Array.isArray(value);
|
|
}
|
|
function isObject(value) {
|
|
return (typeof value === 'object' || typeof value === 'function') && value != null;
|
|
}
|
|
|
|
const DYNAMIC = Symbol();
|
|
const IGNORED = Symbol();
|
|
class AbstractLogic {
|
|
predicates;
|
|
fns = [];
|
|
constructor(predicates) {
|
|
this.predicates = predicates;
|
|
}
|
|
push(logicFn) {
|
|
this.fns.push(wrapWithPredicates(this.predicates, logicFn));
|
|
}
|
|
mergeIn(other) {
|
|
const fns = this.predicates ? other.fns.map(fn => wrapWithPredicates(this.predicates, fn)) : other.fns;
|
|
this.fns.push(...fns);
|
|
}
|
|
}
|
|
class BooleanOrLogic extends AbstractLogic {
|
|
get defaultValue() {
|
|
return false;
|
|
}
|
|
compute(arg) {
|
|
return this.fns.some(f => {
|
|
const result = f(arg);
|
|
return result && result !== IGNORED;
|
|
});
|
|
}
|
|
}
|
|
class ArrayMergeIgnoreLogic extends AbstractLogic {
|
|
ignore;
|
|
static ignoreNull(predicates) {
|
|
return new ArrayMergeIgnoreLogic(predicates, e => e === null);
|
|
}
|
|
constructor(predicates, ignore) {
|
|
super(predicates);
|
|
this.ignore = ignore;
|
|
}
|
|
get defaultValue() {
|
|
return [];
|
|
}
|
|
compute(arg) {
|
|
return this.fns.reduce((prev, f) => {
|
|
const value = f(arg);
|
|
if (value === undefined || value === IGNORED) {
|
|
return prev;
|
|
} else if (isArray(value)) {
|
|
return [...prev, ...(this.ignore ? value.filter(e => !this.ignore(e)) : value)];
|
|
} else {
|
|
if (this.ignore && this.ignore(value)) {
|
|
return prev;
|
|
}
|
|
return [...prev, value];
|
|
}
|
|
}, []);
|
|
}
|
|
}
|
|
class ArrayMergeLogic extends ArrayMergeIgnoreLogic {
|
|
constructor(predicates) {
|
|
super(predicates, undefined);
|
|
}
|
|
}
|
|
class MetadataMergeLogic extends AbstractLogic {
|
|
key;
|
|
get defaultValue() {
|
|
return this.key.reducer.getInitial();
|
|
}
|
|
constructor(predicates, key) {
|
|
super(predicates);
|
|
this.key = key;
|
|
}
|
|
compute(ctx) {
|
|
if (this.fns.length === 0) {
|
|
return this.key.reducer.getInitial();
|
|
}
|
|
let acc = this.key.reducer.getInitial();
|
|
for (let i = 0; i < this.fns.length; i++) {
|
|
const item = this.fns[i](ctx);
|
|
if (item !== IGNORED) {
|
|
acc = this.key.reducer.reduce(acc, item);
|
|
}
|
|
}
|
|
return acc;
|
|
}
|
|
}
|
|
function wrapWithPredicates(predicates, logicFn) {
|
|
if (predicates.length === 0) {
|
|
return logicFn;
|
|
}
|
|
return arg => {
|
|
for (const predicate of predicates) {
|
|
let predicateField = arg.stateOf(predicate.path);
|
|
const depthDiff = untracked(predicateField.structure.pathKeys).length - predicate.depth;
|
|
for (let i = 0; i < depthDiff; i++) {
|
|
predicateField = predicateField.structure.parent;
|
|
}
|
|
if (!predicate.fn(predicateField.context)) {
|
|
return IGNORED;
|
|
}
|
|
}
|
|
return logicFn(arg);
|
|
};
|
|
}
|
|
class LogicContainer {
|
|
predicates;
|
|
hidden;
|
|
disabledReasons;
|
|
readonly;
|
|
syncErrors;
|
|
syncTreeErrors;
|
|
asyncErrors;
|
|
metadata = new Map();
|
|
constructor(predicates) {
|
|
this.predicates = predicates;
|
|
this.hidden = new BooleanOrLogic(predicates);
|
|
this.disabledReasons = new ArrayMergeLogic(predicates);
|
|
this.readonly = new BooleanOrLogic(predicates);
|
|
this.syncErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
|
|
this.syncTreeErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
|
|
this.asyncErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
|
|
}
|
|
hasMetadata(key) {
|
|
return this.metadata.has(key);
|
|
}
|
|
getMetadataKeys() {
|
|
return this.metadata.keys();
|
|
}
|
|
getMetadata(key) {
|
|
if (!this.metadata.has(key)) {
|
|
this.metadata.set(key, new MetadataMergeLogic(this.predicates, key));
|
|
}
|
|
return this.metadata.get(key);
|
|
}
|
|
mergeIn(other) {
|
|
this.hidden.mergeIn(other.hidden);
|
|
this.disabledReasons.mergeIn(other.disabledReasons);
|
|
this.readonly.mergeIn(other.readonly);
|
|
this.syncErrors.mergeIn(other.syncErrors);
|
|
this.syncTreeErrors.mergeIn(other.syncTreeErrors);
|
|
this.asyncErrors.mergeIn(other.asyncErrors);
|
|
for (const key of other.getMetadataKeys()) {
|
|
const metadataLogic = other.metadata.get(key);
|
|
this.getMetadata(key).mergeIn(metadataLogic);
|
|
}
|
|
}
|
|
}
|
|
|
|
class AbstractLogicNodeBuilder {
|
|
depth;
|
|
constructor(depth) {
|
|
this.depth = depth;
|
|
}
|
|
build() {
|
|
return new LeafLogicNode(this, [], 0);
|
|
}
|
|
}
|
|
class LogicNodeBuilder extends AbstractLogicNodeBuilder {
|
|
constructor(depth) {
|
|
super(depth);
|
|
}
|
|
current;
|
|
all = [];
|
|
addHiddenRule(logic) {
|
|
this.getCurrent().addHiddenRule(logic);
|
|
}
|
|
addDisabledReasonRule(logic) {
|
|
this.getCurrent().addDisabledReasonRule(logic);
|
|
}
|
|
addReadonlyRule(logic) {
|
|
this.getCurrent().addReadonlyRule(logic);
|
|
}
|
|
addSyncErrorRule(logic) {
|
|
this.getCurrent().addSyncErrorRule(logic);
|
|
}
|
|
addSyncTreeErrorRule(logic) {
|
|
this.getCurrent().addSyncTreeErrorRule(logic);
|
|
}
|
|
addAsyncErrorRule(logic) {
|
|
this.getCurrent().addAsyncErrorRule(logic);
|
|
}
|
|
addMetadataRule(key, logic) {
|
|
this.getCurrent().addMetadataRule(key, logic);
|
|
}
|
|
getChild(key) {
|
|
if (key === DYNAMIC) {
|
|
const children = this.getCurrent().children;
|
|
if (children.size > (children.has(DYNAMIC) ? 1 : 0)) {
|
|
this.current = undefined;
|
|
}
|
|
}
|
|
return this.getCurrent().getChild(key);
|
|
}
|
|
hasLogic(builder) {
|
|
if (this === builder) {
|
|
return true;
|
|
}
|
|
return this.all.some(({
|
|
builder: subBuilder
|
|
}) => subBuilder.hasLogic(builder));
|
|
}
|
|
mergeIn(other, predicate) {
|
|
if (predicate) {
|
|
this.all.push({
|
|
builder: other,
|
|
predicate: {
|
|
fn: setBoundPathDepthForResolution(predicate.fn, this.depth),
|
|
path: predicate.path
|
|
}
|
|
});
|
|
} else {
|
|
this.all.push({
|
|
builder: other
|
|
});
|
|
}
|
|
this.current = undefined;
|
|
}
|
|
getCurrent() {
|
|
if (this.current === undefined) {
|
|
this.current = new NonMergeableLogicNodeBuilder(this.depth);
|
|
this.all.push({
|
|
builder: this.current
|
|
});
|
|
}
|
|
return this.current;
|
|
}
|
|
static newRoot() {
|
|
return new LogicNodeBuilder(0);
|
|
}
|
|
}
|
|
class NonMergeableLogicNodeBuilder extends AbstractLogicNodeBuilder {
|
|
logic = new LogicContainer([]);
|
|
children = new Map();
|
|
constructor(depth) {
|
|
super(depth);
|
|
}
|
|
addHiddenRule(logic) {
|
|
this.logic.hidden.push(setBoundPathDepthForResolution(logic, this.depth));
|
|
}
|
|
addDisabledReasonRule(logic) {
|
|
this.logic.disabledReasons.push(setBoundPathDepthForResolution(logic, this.depth));
|
|
}
|
|
addReadonlyRule(logic) {
|
|
this.logic.readonly.push(setBoundPathDepthForResolution(logic, this.depth));
|
|
}
|
|
addSyncErrorRule(logic) {
|
|
this.logic.syncErrors.push(setBoundPathDepthForResolution(logic, this.depth));
|
|
}
|
|
addSyncTreeErrorRule(logic) {
|
|
this.logic.syncTreeErrors.push(setBoundPathDepthForResolution(logic, this.depth));
|
|
}
|
|
addAsyncErrorRule(logic) {
|
|
this.logic.asyncErrors.push(setBoundPathDepthForResolution(logic, this.depth));
|
|
}
|
|
addMetadataRule(key, logic) {
|
|
this.logic.getMetadata(key).push(setBoundPathDepthForResolution(logic, this.depth));
|
|
}
|
|
getChild(key) {
|
|
if (!this.children.has(key)) {
|
|
this.children.set(key, new LogicNodeBuilder(this.depth + 1));
|
|
}
|
|
return this.children.get(key);
|
|
}
|
|
hasLogic(builder) {
|
|
return this === builder;
|
|
}
|
|
}
|
|
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 _RuntimeError(1909, ngDevMode && 'Unknown LogicNodeBuilder type');
|
|
}
|
|
}
|
|
function createLogic(builder, predicates, depth) {
|
|
const logic = new LogicContainer(predicates);
|
|
if (builder instanceof LogicNodeBuilder) {
|
|
const builtNodes = builder.all.map(({
|
|
builder,
|
|
predicate
|
|
}) => new LeafLogicNode(builder, predicate ? [...predicates, bindLevel(predicate, depth)] : predicates, depth));
|
|
for (const node of builtNodes) {
|
|
logic.mergeIn(node.logic);
|
|
}
|
|
} else if (builder instanceof NonMergeableLogicNodeBuilder) {
|
|
logic.mergeIn(builder.logic);
|
|
} else {
|
|
throw new _RuntimeError(1909, ngDevMode && 'Unknown LogicNodeBuilder type');
|
|
}
|
|
return logic;
|
|
}
|
|
function bindLevel(predicate, depth) {
|
|
return {
|
|
...predicate,
|
|
depth: depth
|
|
};
|
|
}
|
|
|
|
const PATH = Symbol('PATH');
|
|
class FieldPathNode {
|
|
keys;
|
|
parent;
|
|
keyInParent;
|
|
root;
|
|
children = new Map();
|
|
fieldPathProxy = new Proxy(this, FIELD_PATH_PROXY_HANDLER);
|
|
logicBuilder;
|
|
constructor(keys, root, parent, keyInParent) {
|
|
this.keys = keys;
|
|
this.parent = parent;
|
|
this.keyInParent = keyInParent;
|
|
this.root = root ?? this;
|
|
if (!parent) {
|
|
this.logicBuilder = LogicNodeBuilder.newRoot();
|
|
}
|
|
}
|
|
get builder() {
|
|
if (this.logicBuilder) {
|
|
return this.logicBuilder;
|
|
}
|
|
return this.parent.builder.getChild(this.keyInParent);
|
|
}
|
|
getChild(key) {
|
|
if (!this.children.has(key)) {
|
|
this.children.set(key, new FieldPathNode([...this.keys, key], this.root, this, key));
|
|
}
|
|
return this.children.get(key);
|
|
}
|
|
mergeIn(other, predicate) {
|
|
const path = other.compile();
|
|
this.builder.mergeIn(path.builder, predicate);
|
|
}
|
|
static unwrapFieldPath(formPath) {
|
|
return formPath[PATH];
|
|
}
|
|
static newRoot() {
|
|
return new FieldPathNode([], undefined, undefined, undefined);
|
|
}
|
|
}
|
|
const FIELD_PATH_PROXY_HANDLER = {
|
|
get(node, property) {
|
|
if (property === PATH) {
|
|
return node;
|
|
}
|
|
return node.getChild(property).fieldPathProxy;
|
|
}
|
|
};
|
|
|
|
let currentCompilingNode = undefined;
|
|
const compiledSchemas = new Map();
|
|
class SchemaImpl {
|
|
schemaFn;
|
|
constructor(schemaFn) {
|
|
this.schemaFn = schemaFn;
|
|
}
|
|
compile() {
|
|
if (compiledSchemas.has(this)) {
|
|
return compiledSchemas.get(this);
|
|
}
|
|
const path = FieldPathNode.newRoot();
|
|
compiledSchemas.set(this, path);
|
|
let prevCompilingNode = currentCompilingNode;
|
|
try {
|
|
currentCompilingNode = path;
|
|
this.schemaFn(path.fieldPathProxy);
|
|
} finally {
|
|
currentCompilingNode = prevCompilingNode;
|
|
}
|
|
return path;
|
|
}
|
|
static create(schema) {
|
|
if (schema instanceof SchemaImpl) {
|
|
return schema;
|
|
}
|
|
return new SchemaImpl(schema);
|
|
}
|
|
static rootCompile(schema) {
|
|
try {
|
|
compiledSchemas.clear();
|
|
if (schema === undefined) {
|
|
return FieldPathNode.newRoot();
|
|
}
|
|
if (schema instanceof SchemaImpl) {
|
|
return schema.compile();
|
|
}
|
|
return new SchemaImpl(schema).compile();
|
|
} finally {
|
|
compiledSchemas.clear();
|
|
}
|
|
}
|
|
}
|
|
function isSchemaOrSchemaFn(value) {
|
|
return value instanceof SchemaImpl || typeof value === 'function';
|
|
}
|
|
function assertPathIsCurrent(path) {
|
|
if (currentCompilingNode !== FieldPathNode.unwrapFieldPath(path).root) {
|
|
throw new _RuntimeError(1908, ngDevMode && `A FieldPath can only be used directly within the Schema that owns it, **not** outside of it or within a sub-schema.`);
|
|
}
|
|
}
|
|
|
|
function metadata(path, key, logic) {
|
|
assertPathIsCurrent(path);
|
|
const pathNode = FieldPathNode.unwrapFieldPath(path);
|
|
pathNode.builder.addMetadataRule(key, logic);
|
|
return key;
|
|
}
|
|
const MetadataReducer = {
|
|
list() {
|
|
return {
|
|
reduce: (acc, item) => item === undefined ? acc : [...acc, item],
|
|
getInitial: () => []
|
|
};
|
|
},
|
|
min() {
|
|
return {
|
|
reduce: (acc, item) => {
|
|
if (acc === undefined || item === undefined) {
|
|
return acc ?? item;
|
|
}
|
|
return Math.min(acc, item);
|
|
},
|
|
getInitial: () => undefined
|
|
};
|
|
},
|
|
max() {
|
|
return {
|
|
reduce: (prev, next) => {
|
|
if (prev === undefined || next === undefined) {
|
|
return prev ?? next;
|
|
}
|
|
return Math.max(prev, next);
|
|
},
|
|
getInitial: () => undefined
|
|
};
|
|
},
|
|
or() {
|
|
return {
|
|
reduce: (prev, next) => prev || next,
|
|
getInitial: () => false
|
|
};
|
|
},
|
|
and() {
|
|
return {
|
|
reduce: (prev, next) => prev && next,
|
|
getInitial: () => true
|
|
};
|
|
},
|
|
override
|
|
};
|
|
function override(getInitial) {
|
|
return {
|
|
reduce: (_, item) => item,
|
|
getInitial: () => getInitial?.()
|
|
};
|
|
}
|
|
class MetadataKey {
|
|
reducer;
|
|
create;
|
|
brand;
|
|
constructor(reducer, create) {
|
|
this.reducer = reducer;
|
|
this.create = create;
|
|
}
|
|
}
|
|
function createMetadataKey(reducer) {
|
|
return new MetadataKey(reducer ?? MetadataReducer.override());
|
|
}
|
|
function createManagedMetadataKey(create, reducer) {
|
|
return new MetadataKey(reducer ?? MetadataReducer.override(), create);
|
|
}
|
|
const REQUIRED = createMetadataKey(MetadataReducer.or());
|
|
const MIN = createMetadataKey(MetadataReducer.max());
|
|
const MAX = createMetadataKey(MetadataReducer.min());
|
|
const MIN_LENGTH = createMetadataKey(MetadataReducer.max());
|
|
const MAX_LENGTH = createMetadataKey(MetadataReducer.min());
|
|
const PATTERN = createMetadataKey(MetadataReducer.list());
|
|
|
|
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.submissionErrors())];
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "syncErrors"
|
|
}] : []));
|
|
syncValid = computed(() => {
|
|
if (this.shouldSkipValidation()) {
|
|
return true;
|
|
}
|
|
return this.node.structure.reduceChildren(this.syncErrors().length === 0, (child, value) => value && child.validationState.syncValid(), shortCircuitFalse);
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "syncValid"
|
|
}] : []));
|
|
syncTreeErrors = computed(() => this.rawSyncTreeErrors().filter(err => err.fieldTree === this.node.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.fieldTree === this.node.fieldProxy);
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "asyncErrors"
|
|
}] : []));
|
|
errors = computed(() => [...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], ...(ngDevMode ? [{
|
|
debugName: "errors"
|
|
}] : []));
|
|
errorSummary = computed(() => this.node.structure.reduceChildren(this.errors(), (child, result) => [...result, ...child.errorSummary()]), ...(ngDevMode ? [{
|
|
debugName: "errorSummary"
|
|
}] : []));
|
|
pending = computed(() => this.node.structure.reduceChildren(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 this.node.structure.reduceChildren(ownStatus, (child, value) => {
|
|
if (value === 'invalid' || child.validationState.status() === 'invalid') {
|
|
return 'invalid';
|
|
} else if (value === 'unknown' || child.validationState.status() === 'unknown') {
|
|
return 'unknown';
|
|
}
|
|
return 'valid';
|
|
}, v => v === 'invalid');
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "status"
|
|
}] : []));
|
|
valid = computed(() => this.status() === 'valid', ...(ngDevMode ? [{
|
|
debugName: "valid"
|
|
}] : []));
|
|
invalid = computed(() => this.status() === 'invalid', ...(ngDevMode ? [{
|
|
debugName: "invalid"
|
|
}] : []));
|
|
shouldSkipValidation = computed(() => this.node.hidden() || this.node.disabled() || this.node.readonly(), ...(ngDevMode ? [{
|
|
debugName: "shouldSkipValidation"
|
|
}] : []));
|
|
}
|
|
function normalizeErrors(error) {
|
|
if (error === undefined) {
|
|
return [];
|
|
}
|
|
if (isArray(error)) {
|
|
return error;
|
|
}
|
|
return [error];
|
|
}
|
|
function addDefaultField(errors, fieldTree) {
|
|
if (isArray(errors)) {
|
|
for (const error of errors) {
|
|
error.fieldTree ??= fieldTree;
|
|
}
|
|
} else if (errors) {
|
|
errors.fieldTree ??= fieldTree;
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
const DEBOUNCER = createMetadataKey();
|
|
|
|
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 _RuntimeError(1900, ngDevMode && 'Path is not part of this field tree.');
|
|
}
|
|
}
|
|
for (let key of targetPathNode.keys) {
|
|
field = field.structure.getChild(key);
|
|
if (field === undefined) {
|
|
throw new _RuntimeError(1901, ngDevMode && `Cannot resolve path .${targetPathNode.keys.join('.')} relative to field ${['<root>', ...this.node.structure.pathKeys()].join('.')}.`);
|
|
}
|
|
}
|
|
return field.fieldProxy;
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "resolver"
|
|
}] : []));
|
|
this.cache.set(target, resolver);
|
|
}
|
|
return this.cache.get(target)();
|
|
}
|
|
get fieldTree() {
|
|
return this.node.fieldProxy;
|
|
}
|
|
get state() {
|
|
return this.node;
|
|
}
|
|
get value() {
|
|
return this.node.structure.value;
|
|
}
|
|
get key() {
|
|
return this.node.structure.keyInParent;
|
|
}
|
|
get pathKeys() {
|
|
return this.node.structure.pathKeys;
|
|
}
|
|
index = computed(() => {
|
|
const key = this.key();
|
|
if (!isArray(untracked(this.node.structure.parent.value))) {
|
|
throw new _RuntimeError(1906, ngDevMode && 'Cannot access index, parent field is not an array.');
|
|
}
|
|
return Number(key);
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "index"
|
|
}] : []));
|
|
fieldTreeOf = p => this.resolve(p);
|
|
stateOf = p => this.resolve(p)();
|
|
valueOf = p => {
|
|
const result = this.resolve(p)().value();
|
|
if (result instanceof AbstractControl) {
|
|
throw new _RuntimeError(1907, ngDevMode && `Tried to read an 'AbstractControl' value from a 'form()'. Did you mean to use 'compatForm()' instead?`);
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
class FieldMetadataState {
|
|
node;
|
|
metadata = new Map();
|
|
constructor(node) {
|
|
this.node = node;
|
|
for (const key of this.node.logicNode.logic.getMetadataKeys()) {
|
|
if (key.create) {
|
|
const logic = this.node.logicNode.logic.getMetadata(key);
|
|
const result = untracked(() => runInInjectionContext(this.node.structure.injector, () => key.create(computed(() => logic.compute(this.node.context)))));
|
|
this.metadata.set(key, result);
|
|
}
|
|
}
|
|
}
|
|
get(key) {
|
|
if (this.has(key)) {
|
|
if (!this.metadata.has(key)) {
|
|
if (key.create) {
|
|
throw new _RuntimeError(1912, ngDevMode && 'Managed metadata cannot be created lazily');
|
|
}
|
|
const logic = this.node.logicNode.logic.getMetadata(key);
|
|
this.metadata.set(key, computed(() => logic.compute(this.node.context)));
|
|
}
|
|
}
|
|
return this.metadata.get(key);
|
|
}
|
|
has(key) {
|
|
return this.node.logicNode.logic.hasMetadata(key);
|
|
}
|
|
}
|
|
|
|
const FIELD_PROXY_HANDLER = {
|
|
get(getTgt, 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 () => {
|
|
tgt.value();
|
|
return Array.prototype[Symbol.iterator].apply(tgt.fieldProxy);
|
|
};
|
|
}
|
|
}
|
|
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;
|
|
node;
|
|
createChildNode;
|
|
identitySymbol = Symbol();
|
|
_injector = undefined;
|
|
get injector() {
|
|
this._injector ??= Injector.create({
|
|
providers: [],
|
|
parent: this.fieldManager.injector
|
|
});
|
|
return this._injector;
|
|
}
|
|
constructor(logic, node, createChildNode) {
|
|
this.logic = logic;
|
|
this.node = node;
|
|
this.createChildNode = createChildNode;
|
|
}
|
|
children() {
|
|
const map = this.childrenMap();
|
|
if (map === undefined) {
|
|
return [];
|
|
}
|
|
return Array.from(map.byPropertyKey.values()).map(child => untracked(child.reader));
|
|
}
|
|
getChild(key) {
|
|
const strKey = key.toString();
|
|
let reader = untracked(this.childrenMap)?.byPropertyKey.get(strKey)?.reader;
|
|
if (!reader) {
|
|
reader = this.createReader(strKey);
|
|
}
|
|
return reader();
|
|
}
|
|
reduceChildren(initialValue, fn, shortCircuit) {
|
|
const map = this.childrenMap();
|
|
if (!map) {
|
|
return initialValue;
|
|
}
|
|
let value = initialValue;
|
|
for (const child of map.byPropertyKey.values()) {
|
|
if (shortCircuit?.(value)) {
|
|
break;
|
|
}
|
|
value = fn(untracked(child.reader), value);
|
|
}
|
|
return value;
|
|
}
|
|
destroy() {
|
|
this.injector.destroy();
|
|
}
|
|
createKeyInParent(options, identityInParent, initialKeyInParent) {
|
|
if (options.kind === 'root') {
|
|
return ROOT_KEY_IN_PARENT;
|
|
}
|
|
if (identityInParent === undefined) {
|
|
const key = initialKeyInParent;
|
|
return computed(() => {
|
|
if (this.parent.structure.getChild(key) !== this.node) {
|
|
throw new _RuntimeError(1902, ngDevMode && `Orphan field, looking for property '${key}' of ${getDebugName(this.parent)}`);
|
|
}
|
|
return key;
|
|
});
|
|
} else {
|
|
let lastKnownKey = initialKeyInParent;
|
|
return computed(() => {
|
|
const parentValue = this.parent.structure.value();
|
|
if (!isArray(parentValue)) {
|
|
throw new _RuntimeError(1903, ngDevMode && `Orphan field, expected ${getDebugName(this.parent)} to be an array`);
|
|
}
|
|
const data = parentValue[lastKnownKey];
|
|
if (isObject(data) && data.hasOwnProperty(this.parent.structure.identitySymbol) && data[this.parent.structure.identitySymbol] === identityInParent) {
|
|
return lastKnownKey;
|
|
}
|
|
for (let i = 0; i < parentValue.length; i++) {
|
|
const data = parentValue[i];
|
|
if (isObject(data) && data.hasOwnProperty(this.parent.structure.identitySymbol) && data[this.parent.structure.identitySymbol] === identityInParent) {
|
|
return lastKnownKey = i.toString();
|
|
}
|
|
}
|
|
throw new _RuntimeError(1904, ngDevMode && `Orphan field, can't find element in array ${getDebugName(this.parent)}`);
|
|
});
|
|
}
|
|
}
|
|
createChildrenMap() {
|
|
return linkedSignal({
|
|
source: this.value,
|
|
computation: (value, previous) => {
|
|
if (!isObject(value)) {
|
|
return undefined;
|
|
}
|
|
const prevData = previous?.value ?? {
|
|
byPropertyKey: new Map()
|
|
};
|
|
let data;
|
|
const parentIsArray = isArray(value);
|
|
if (prevData !== undefined) {
|
|
if (parentIsArray) {
|
|
data = maybeRemoveStaleArrayFields(prevData, value, this.identitySymbol);
|
|
} else {
|
|
data = maybeRemoveStaleObjectFields(prevData, value);
|
|
}
|
|
}
|
|
for (const key of Object.keys(value)) {
|
|
let trackingKey = undefined;
|
|
const childValue = value[key];
|
|
if (childValue === undefined) {
|
|
if (prevData.byPropertyKey.has(key)) {
|
|
data ??= {
|
|
...prevData
|
|
};
|
|
data.byPropertyKey.delete(key);
|
|
}
|
|
continue;
|
|
}
|
|
if (parentIsArray && isObject(childValue) && !isArray(childValue)) {
|
|
trackingKey = childValue[this.identitySymbol] ??= Symbol(ngDevMode ? `id:${globalId++}` : '');
|
|
}
|
|
let childNode;
|
|
if (trackingKey) {
|
|
if (!prevData.byTrackingKey?.has(trackingKey)) {
|
|
data ??= {
|
|
...prevData
|
|
};
|
|
data.byTrackingKey ??= new Map();
|
|
data.byTrackingKey.set(trackingKey, this.createChildNode(key, trackingKey, parentIsArray));
|
|
}
|
|
childNode = (data ?? prevData).byTrackingKey.get(trackingKey);
|
|
}
|
|
const child = prevData.byPropertyKey.get(key);
|
|
if (child === undefined) {
|
|
data ??= {
|
|
...prevData
|
|
};
|
|
data.byPropertyKey.set(key, {
|
|
reader: this.createReader(key),
|
|
node: childNode ?? this.createChildNode(key, trackingKey, parentIsArray)
|
|
});
|
|
} else if (childNode && childNode !== child.node) {
|
|
data ??= {
|
|
...prevData
|
|
};
|
|
child.node = childNode;
|
|
}
|
|
}
|
|
return data ?? prevData;
|
|
}
|
|
});
|
|
}
|
|
createReader(key) {
|
|
return computed(() => this.childrenMap()?.byPropertyKey.get(key)?.node);
|
|
}
|
|
}
|
|
class RootFieldNodeStructure extends FieldNodeStructure {
|
|
fieldManager;
|
|
value;
|
|
get parent() {
|
|
return undefined;
|
|
}
|
|
get root() {
|
|
return this.node;
|
|
}
|
|
get pathKeys() {
|
|
return ROOT_PATH_KEYS;
|
|
}
|
|
get keyInParent() {
|
|
return ROOT_KEY_IN_PARENT;
|
|
}
|
|
childrenMap;
|
|
constructor(node, logic, fieldManager, value, createChildNode) {
|
|
super(logic, node, createChildNode);
|
|
this.fieldManager = fieldManager;
|
|
this.value = value;
|
|
this.childrenMap = this.createChildrenMap();
|
|
}
|
|
}
|
|
class ChildFieldNodeStructure extends FieldNodeStructure {
|
|
logic;
|
|
parent;
|
|
root;
|
|
pathKeys;
|
|
keyInParent;
|
|
value;
|
|
childrenMap;
|
|
get fieldManager() {
|
|
return this.root.structure.fieldManager;
|
|
}
|
|
constructor(node, logic, parent, identityInParent, initialKeyInParent, createChildNode) {
|
|
super(logic, node, createChildNode);
|
|
this.logic = logic;
|
|
this.parent = parent;
|
|
this.root = this.parent.structure.root;
|
|
this.keyInParent = this.createKeyInParent({
|
|
kind: 'child',
|
|
parent,
|
|
pathNode: undefined,
|
|
logic,
|
|
initialKeyInParent,
|
|
identityInParent,
|
|
fieldAdapter: undefined
|
|
}, identityInParent, initialKeyInParent);
|
|
this.pathKeys = computed(() => [...parent.structure.pathKeys(), this.keyInParent()], ...(ngDevMode ? [{
|
|
debugName: "pathKeys"
|
|
}] : []));
|
|
this.value = deepSignal(this.parent.structure.value, this.keyInParent);
|
|
this.childrenMap = this.createChildrenMap();
|
|
this.fieldManager.structures.add(this);
|
|
}
|
|
}
|
|
let globalId = 0;
|
|
const ROOT_PATH_KEYS = computed(() => [], ...(ngDevMode ? [{
|
|
debugName: "ROOT_PATH_KEYS"
|
|
}] : []));
|
|
const ROOT_KEY_IN_PARENT = computed(() => {
|
|
throw new _RuntimeError(1905, ngDevMode && 'The top-level field in the form has no parent.');
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "ROOT_KEY_IN_PARENT"
|
|
}] : []));
|
|
function getDebugName(node) {
|
|
return `<root>.${node.structure.pathKeys().join('.')}`;
|
|
}
|
|
function maybeRemoveStaleArrayFields(prevData, value, identitySymbol) {
|
|
let data;
|
|
const oldKeys = new Set(prevData.byPropertyKey.keys());
|
|
const oldTracking = new Set(prevData.byTrackingKey?.keys());
|
|
for (let i = 0; i < value.length; i++) {
|
|
const childValue = value[i];
|
|
oldKeys.delete(i.toString());
|
|
if (isObject(childValue) && childValue.hasOwnProperty(identitySymbol)) {
|
|
oldTracking.delete(childValue[identitySymbol]);
|
|
}
|
|
}
|
|
if (oldKeys.size > 0) {
|
|
data ??= {
|
|
...prevData
|
|
};
|
|
for (const key of oldKeys) {
|
|
data.byPropertyKey.delete(key);
|
|
}
|
|
}
|
|
if (oldTracking.size > 0) {
|
|
data ??= {
|
|
...prevData
|
|
};
|
|
for (const id of oldTracking) {
|
|
data.byTrackingKey?.delete(id);
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
function maybeRemoveStaleObjectFields(prevData, value) {
|
|
let data;
|
|
for (const key of prevData.byPropertyKey.keys()) {
|
|
if (!value.hasOwnProperty(key)) {
|
|
data ??= {
|
|
...prevData
|
|
};
|
|
data.byPropertyKey.delete(key);
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
class FieldSubmitState {
|
|
node;
|
|
selfSubmitting = signal(false, ...(ngDevMode ? [{
|
|
debugName: "selfSubmitting"
|
|
}] : []));
|
|
submissionErrors;
|
|
constructor(node) {
|
|
this.node = node;
|
|
this.submissionErrors = linkedSignal({
|
|
...(ngDevMode ? {
|
|
debugName: "submissionErrors"
|
|
} : {}),
|
|
source: this.node.structure.value,
|
|
computation: () => []
|
|
});
|
|
}
|
|
submitting = computed(() => {
|
|
return this.selfSubmitting() || (this.node.structure.parent?.submitting() ?? false);
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "submitting"
|
|
}] : []));
|
|
}
|
|
|
|
class FieldNode {
|
|
structure;
|
|
validationState;
|
|
metadataState;
|
|
nodeState;
|
|
submitState;
|
|
fieldAdapter;
|
|
_context = undefined;
|
|
get context() {
|
|
return this._context ??= new FieldNodeContext(this);
|
|
}
|
|
fieldProxy = new Proxy(() => this, FIELD_PROXY_HANDLER);
|
|
pathNode;
|
|
constructor(options) {
|
|
this.pathNode = options.pathNode;
|
|
this.fieldAdapter = options.fieldAdapter;
|
|
this.structure = this.fieldAdapter.createStructure(this, options);
|
|
this.validationState = this.fieldAdapter.createValidationState(this, options);
|
|
this.nodeState = this.fieldAdapter.createNodeState(this, options);
|
|
this.metadataState = new FieldMetadataState(this);
|
|
this.submitState = new FieldSubmitState(this);
|
|
}
|
|
focusBoundControl() {
|
|
this.getBindingForFocus()?.focus();
|
|
}
|
|
getBindingForFocus() {
|
|
const own = this.formFieldBindings().filter(b => b.focus !== undefined).reduce(firstInDom, undefined);
|
|
if (own) return own;
|
|
return this.structure.children().map(child => child.getBindingForFocus()).reduce(firstInDom, undefined);
|
|
}
|
|
pendingSync = linkedSignal({
|
|
...(ngDevMode ? {
|
|
debugName: "pendingSync"
|
|
} : {}),
|
|
source: () => this.value(),
|
|
computation: (_source, previous) => {
|
|
previous?.value?.abort();
|
|
return undefined;
|
|
}
|
|
});
|
|
get 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 formFieldBindings() {
|
|
return this.nodeState.formFieldBindings;
|
|
}
|
|
get submitting() {
|
|
return this.submitState.submitting;
|
|
}
|
|
get name() {
|
|
return this.nodeState.name;
|
|
}
|
|
get max() {
|
|
return this.metadata(MAX);
|
|
}
|
|
get maxLength() {
|
|
return this.metadata(MAX_LENGTH);
|
|
}
|
|
get min() {
|
|
return this.metadata(MIN);
|
|
}
|
|
get minLength() {
|
|
return this.metadata(MIN_LENGTH);
|
|
}
|
|
get pattern() {
|
|
return this.metadata(PATTERN) ?? EMPTY;
|
|
}
|
|
get required() {
|
|
return this.metadata(REQUIRED) ?? FALSE;
|
|
}
|
|
metadata(key) {
|
|
return this.metadataState.get(key);
|
|
}
|
|
hasMetadata(key) {
|
|
return this.metadataState.has(key);
|
|
}
|
|
markAsTouched() {
|
|
untracked(() => {
|
|
this.nodeState.markAsTouched();
|
|
this.flushSync();
|
|
});
|
|
}
|
|
markAsDirty() {
|
|
this.nodeState.markAsDirty();
|
|
}
|
|
reset(value) {
|
|
untracked(() => this._reset(value));
|
|
}
|
|
_reset(value) {
|
|
if (value !== undefined) {
|
|
this.value.set(value);
|
|
}
|
|
this.nodeState.markAsUntouched();
|
|
this.nodeState.markAsPristine();
|
|
for (const child of this.structure.children()) {
|
|
child._reset();
|
|
}
|
|
}
|
|
setControlValue(newValue) {
|
|
untracked(() => {
|
|
this._controlValue.set(newValue);
|
|
this.markAsDirty();
|
|
this.debounceSync();
|
|
});
|
|
}
|
|
sync() {
|
|
this.value.set(this.controlValue());
|
|
}
|
|
flushSync() {
|
|
const pending = this.pendingSync();
|
|
if (pending && !pending.signal.aborted) {
|
|
pending.abort();
|
|
this.sync();
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
createStructure(options) {
|
|
return options.kind === 'root' ? new RootFieldNodeStructure(this, options.logic, options.fieldManager, options.value, this.newChild.bind(this)) : new ChildFieldNodeStructure(this, options.logic, options.parent, options.identityInParent, options.initialKeyInParent, this.newChild.bind(this));
|
|
}
|
|
newChild(key, trackingId, isArray) {
|
|
let childPath;
|
|
let childLogic;
|
|
if (isArray) {
|
|
childPath = this.pathNode.getChild(DYNAMIC);
|
|
childLogic = this.structure.logic.getChild(DYNAMIC);
|
|
} else {
|
|
childPath = this.pathNode.getChild(key);
|
|
childLogic = this.structure.logic.getChild(key);
|
|
}
|
|
return this.fieldAdapter.newChild({
|
|
kind: 'child',
|
|
parent: this,
|
|
pathNode: childPath,
|
|
logic: childLogic,
|
|
initialKeyInParent: key,
|
|
identityInParent: trackingId,
|
|
fieldAdapter: this.fieldAdapter
|
|
});
|
|
}
|
|
}
|
|
const EMPTY = computed(() => [], ...(ngDevMode ? [{
|
|
debugName: "EMPTY"
|
|
}] : []));
|
|
const FALSE = computed(() => false, ...(ngDevMode ? [{
|
|
debugName: "FALSE"
|
|
}] : []));
|
|
function firstInDom(a, b) {
|
|
if (!a) return b;
|
|
if (!b) return a;
|
|
const position = a.element.compareDocumentPosition(b.element);
|
|
return position & Node.DOCUMENT_POSITION_PRECEDING ? b : a;
|
|
}
|
|
|
|
class FieldNodeState {
|
|
node;
|
|
selfTouched = signal(false, ...(ngDevMode ? [{
|
|
debugName: "selfTouched"
|
|
}] : []));
|
|
selfDirty = signal(false, ...(ngDevMode ? [{
|
|
debugName: "selfDirty"
|
|
}] : []));
|
|
markAsTouched() {
|
|
this.selfTouched.set(true);
|
|
}
|
|
markAsDirty() {
|
|
this.selfDirty.set(true);
|
|
}
|
|
markAsPristine() {
|
|
this.selfDirty.set(false);
|
|
}
|
|
markAsUntouched() {
|
|
this.selfTouched.set(false);
|
|
}
|
|
formFieldBindings = signal([], ...(ngDevMode ? [{
|
|
debugName: "formFieldBindings"
|
|
}] : []));
|
|
constructor(node) {
|
|
this.node = node;
|
|
}
|
|
dirty = computed(() => {
|
|
const selfDirtyValue = this.selfDirty() && !this.isNonInteractive();
|
|
return this.node.structure.reduceChildren(selfDirtyValue, (child, value) => value || child.nodeState.dirty(), shortCircuitTrue);
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "dirty"
|
|
}] : []));
|
|
touched = computed(() => {
|
|
const selfTouchedValue = this.selfTouched() && !this.isNonInteractive();
|
|
return this.node.structure.reduceChildren(selfTouchedValue, (child, value) => value || child.nodeState.touched(), shortCircuitTrue);
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "touched"
|
|
}] : []));
|
|
disabledReasons = computed(() => [...(this.node.structure.parent?.nodeState.disabledReasons() ?? []), ...this.node.logicNode.logic.disabledReasons.compute(this.node.context)], ...(ngDevMode ? [{
|
|
debugName: "disabledReasons"
|
|
}] : []));
|
|
disabled = computed(() => !!this.disabledReasons().length, ...(ngDevMode ? [{
|
|
debugName: "disabled"
|
|
}] : []));
|
|
readonly = computed(() => (this.node.structure.parent?.nodeState.readonly() || this.node.logicNode.logic.readonly.compute(this.node.context)) ?? false, ...(ngDevMode ? [{
|
|
debugName: "readonly"
|
|
}] : []));
|
|
hidden = computed(() => (this.node.structure.parent?.nodeState.hidden() || this.node.logicNode.logic.hidden.compute(this.node.context)) ?? false, ...(ngDevMode ? [{
|
|
debugName: "hidden"
|
|
}] : []));
|
|
name = computed(() => {
|
|
const parent = this.node.structure.parent;
|
|
if (!parent) {
|
|
return this.node.structure.fieldManager.rootName;
|
|
}
|
|
return `${parent.name()}.${this.node.structure.keyInParent()}`;
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "name"
|
|
}] : []));
|
|
debouncer = computed(() => {
|
|
if (this.node.logicNode.logic.hasMetadata(DEBOUNCER)) {
|
|
const debouncerLogic = this.node.logicNode.logic.getMetadata(DEBOUNCER);
|
|
const debouncer = debouncerLogic.compute(this.node.context);
|
|
if (debouncer) {
|
|
return signal => debouncer(this.node.context, signal);
|
|
}
|
|
}
|
|
return this.node.structure.parent?.nodeState.debouncer?.();
|
|
}, ...(ngDevMode ? [{
|
|
debugName: "debouncer"
|
|
}] : []));
|
|
isNonInteractive = computed(() => this.hidden() || this.disabled() || this.readonly(), ...(ngDevMode ? [{
|
|
debugName: "isNonInteractive"
|
|
}] : []));
|
|
}
|
|
|
|
class BasicFieldAdapter {
|
|
newRoot(fieldManager, value, pathNode, adapter) {
|
|
return new FieldNode({
|
|
kind: 'root',
|
|
fieldManager,
|
|
value,
|
|
pathNode,
|
|
logic: pathNode.builder.build(),
|
|
fieldAdapter: adapter
|
|
});
|
|
}
|
|
newChild(options) {
|
|
return new FieldNode(options);
|
|
}
|
|
createNodeState(node) {
|
|
return new FieldNodeState(node);
|
|
}
|
|
createValidationState(node) {
|
|
return new FieldValidationState(node);
|
|
}
|
|
createStructure(node, options) {
|
|
return node.createStructure(options);
|
|
}
|
|
}
|
|
|
|
class FormFieldManager {
|
|
injector;
|
|
rootName;
|
|
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 && setSubmissionErrors(node, errors);
|
|
} finally {
|
|
node.submitState.selfSubmitting.set(false);
|
|
}
|
|
}
|
|
function setSubmissionErrors(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.fieldTree();
|
|
let fieldErrors = errorsByField.get(field);
|
|
if (!fieldErrors) {
|
|
fieldErrors = [];
|
|
errorsByField.set(field, fieldErrors);
|
|
}
|
|
fieldErrors.push(errorWithField);
|
|
}
|
|
for (const [field, fieldErrors] of errorsByField) {
|
|
field.submitState.submissionErrors.set(fieldErrors);
|
|
}
|
|
}
|
|
function schema(fn) {
|
|
return SchemaImpl.create(fn);
|
|
}
|
|
function markAllAsTouched(node) {
|
|
node.markAsTouched();
|
|
for (const child of node.structure.children()) {
|
|
markAllAsTouched(child);
|
|
}
|
|
}
|
|
|
|
export { BasicFieldAdapter, DEBOUNCER, FieldNode, FieldNodeState, FieldNodeStructure, FieldPathNode, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MetadataKey, MetadataReducer, PATTERN, REQUIRED, addDefaultField, apply, applyEach, applyWhen, applyWhenValue, assertPathIsCurrent, calculateValidationSelfStatus, createManagedMetadataKey, createMetadataKey, form, getInjectorFromOptions, metadata, normalizeFormArgs, schema, submit };
|
|
//# sourceMappingURL=_structure-chunk.mjs.map
|
|
|