@ngx-formly/core
Version:
Formly is a dynamic (JSON powered) form library for Angular that bring unmatched maintainability to your application's forms.
1,188 lines (1,179 loc) • 102 kB
JavaScript
import * as i0 from '@angular/core';
import { Type, TemplateRef, ComponentRef, ChangeDetectorRef, InjectionToken, Injectable, Optional, Directive, Input, ViewContainerRef, Component, ViewChild, EventEmitter, ChangeDetectionStrategy, Output, ContentChildren, Inject, ViewChildren, NgModule } from '@angular/core';
import * as i2 from '@angular/forms';
import { AbstractControl, FormGroup, FormArray, FormControl, NgControl, Validators } from '@angular/forms';
import { isObservable, merge, of, Observable, Subject } from 'rxjs';
import { distinctUntilChanged, startWith, debounceTime, filter, switchMap, take, tap, map } from 'rxjs/operators';
import * as i2$1 from '@angular/common';
import { DOCUMENT, CommonModule } from '@angular/common';
import * as i1 from '@angular/platform-browser';
import { __rest } from 'tslib';
function disableTreeValidityCall(form, callback) {
const _updateTreeValidity = form._updateTreeValidity.bind(form);
form._updateTreeValidity = () => { };
callback();
form._updateTreeValidity = _updateTreeValidity;
}
function getFieldId(formId, field, index) {
if (field.id) {
return field.id;
}
let type = field.type;
if (!type && field.template) {
type = 'template';
}
if (type instanceof Type) {
type = type.prototype.constructor.name;
}
return [formId, type, field.key, index].join('_');
}
function hasKey(field) {
return !isNil(field.key) && field.key !== '';
}
function getKeyPath(field) {
var _a;
if (!hasKey(field)) {
return [];
}
/* We store the keyPath in the field for performance reasons. This function will be called frequently. */
if (((_a = field._keyPath) === null || _a === void 0 ? void 0 : _a.key) !== field.key) {
let path = [];
if (typeof field.key === 'string') {
const key = field.key.indexOf('[') === -1 ? field.key : field.key.replace(/\[(\w+)\]/g, '.$1');
path = key.indexOf('.') !== -1 ? key.split('.') : [key];
}
else if (Array.isArray(field.key)) {
path = field.key.slice(0);
}
else {
path = [`${field.key}`];
}
defineHiddenProp(field, '_keyPath', { key: field.key, path });
}
return field._keyPath.path.slice(0);
}
const FORMLY_VALIDATORS = ['required', 'pattern', 'minLength', 'maxLength', 'min', 'max'];
function assignFieldValue(field, value) {
let paths = getKeyPath(field);
if (paths.length === 0) {
return;
}
let root = field;
while (root.parent) {
root = root.parent;
paths = [...getKeyPath(root), ...paths];
}
if (value === undefined && field.resetOnHide) {
const k = paths.pop();
const m = paths.reduce((model, path) => model[path] || {}, root.model);
delete m[k];
return;
}
assignModelValue(root.model, paths, value);
}
function assignModelValue(model, paths, value) {
for (let i = 0; i < paths.length - 1; i++) {
const path = paths[i];
if (!model[path] || !isObject(model[path])) {
model[path] = /^\d+$/.test(paths[i + 1]) ? [] : {};
}
model = model[path];
}
model[paths[paths.length - 1]] = clone(value);
}
function getFieldValue(field) {
let model = field.parent ? field.parent.model : field.model;
for (const path of getKeyPath(field)) {
if (!model) {
return model;
}
model = model[path];
}
return model;
}
function reverseDeepMerge(dest, ...args) {
args.forEach((src) => {
for (const srcArg in src) {
if (isNil(dest[srcArg]) || isBlankString(dest[srcArg])) {
dest[srcArg] = clone(src[srcArg]);
}
else if (objAndSameType(dest[srcArg], src[srcArg])) {
reverseDeepMerge(dest[srcArg], src[srcArg]);
}
}
});
return dest;
}
// check a value is null or undefined
function isNil(value) {
return value == null;
}
function isUndefined(value) {
return value === undefined;
}
function isBlankString(value) {
return value === '';
}
function isFunction(value) {
return typeof value === 'function';
}
function objAndSameType(obj1, obj2) {
return (isObject(obj1) &&
isObject(obj2) &&
Object.getPrototypeOf(obj1) === Object.getPrototypeOf(obj2) &&
!(Array.isArray(obj1) || Array.isArray(obj2)));
}
function isObject(x) {
return x != null && typeof x === 'object';
}
function isPromise(obj) {
return !!obj && typeof obj.then === 'function';
}
function clone(value) {
if (!isObject(value) ||
isObservable(value) ||
value instanceof TemplateRef ||
/* instanceof SafeHtmlImpl */ value.changingThisBreaksApplicationSecurity ||
['RegExp', 'FileList', 'File', 'Blob'].indexOf(value.constructor.name) !== -1) {
return value;
}
if (value instanceof Set) {
return new Set(value);
}
if (value instanceof Map) {
return new Map(value);
}
// https://github.com/moment/moment/blob/master/moment.js#L252
if (value._isAMomentObject && isFunction(value.clone)) {
return value.clone();
}
if (value instanceof AbstractControl) {
return null;
}
if (value instanceof Date) {
return new Date(value.getTime());
}
if (Array.isArray(value)) {
return value.slice(0).map((v) => clone(v));
}
// best way to clone a js object maybe
// https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance
const proto = Object.getPrototypeOf(value);
let c = Object.create(proto);
c = Object.setPrototypeOf(c, proto);
// need to make a deep copy so we dont use Object.assign
// also Object.assign wont copy property descriptor exactly
return Object.keys(value).reduce((newVal, prop) => {
const propDesc = Object.getOwnPropertyDescriptor(value, prop);
if (propDesc.get) {
Object.defineProperty(newVal, prop, propDesc);
}
else {
newVal[prop] = clone(value[prop]);
}
return newVal;
}, c);
}
function defineHiddenProp(field, prop, defaultValue) {
Object.defineProperty(field, prop, { enumerable: false, writable: true, configurable: true });
field[prop] = defaultValue;
}
function observeDeep(source, paths, setFn) {
let observers = [];
const unsubscribe = () => {
observers.forEach((observer) => observer());
observers = [];
};
const observer = observe(source, paths, ({ firstChange, currentValue }) => {
!firstChange && setFn();
unsubscribe();
if (isObject(currentValue) && currentValue.constructor.name === 'Object') {
Object.keys(currentValue).forEach((prop) => {
observers.push(observeDeep(source, [...paths, prop], setFn));
});
}
});
return () => {
observer.unsubscribe();
unsubscribe();
};
}
function observe(o, paths, setFn) {
if (!o._observers) {
defineHiddenProp(o, '_observers', {});
}
let target = o;
for (let i = 0; i < paths.length - 1; i++) {
if (!target[paths[i]] || !isObject(target[paths[i]])) {
target[paths[i]] = /^\d+$/.test(paths[i + 1]) ? [] : {};
}
target = target[paths[i]];
}
const key = paths[paths.length - 1];
const prop = paths.join('.');
if (!o._observers[prop]) {
o._observers[prop] = { value: target[key], onChange: [] };
}
const state = o._observers[prop];
if (target[key] !== state.value) {
state.value = target[key];
}
if (setFn && state.onChange.indexOf(setFn) === -1) {
state.onChange.push(setFn);
setFn({ currentValue: state.value, firstChange: true });
if (state.onChange.length >= 1 && isObject(target)) {
const { enumerable } = Object.getOwnPropertyDescriptor(target, key) || { enumerable: true };
Object.defineProperty(target, key, {
enumerable,
configurable: true,
get: () => state.value,
set: (currentValue) => {
if (currentValue !== state.value) {
const previousValue = state.value;
state.value = currentValue;
state.onChange.forEach((changeFn) => changeFn({ previousValue, currentValue, firstChange: false }));
}
},
});
}
}
return {
setValue(currentValue, emitEvent = true) {
if (currentValue === state.value) {
return;
}
const previousValue = state.value;
state.value = currentValue;
state.onChange.forEach((changeFn) => {
if (changeFn !== setFn && emitEvent) {
changeFn({ previousValue, currentValue, firstChange: false });
}
});
},
unsubscribe() {
state.onChange = state.onChange.filter((changeFn) => changeFn !== setFn);
if (state.onChange.length === 0) {
delete o._observers[prop];
}
},
};
}
function getField(f, key) {
key = (Array.isArray(key) ? key.join('.') : key);
if (!f.fieldGroup) {
return undefined;
}
for (let i = 0, len = f.fieldGroup.length; i < len; i++) {
const c = f.fieldGroup[i];
const k = (Array.isArray(c.key) ? c.key.join('.') : c.key);
if (k === key) {
return c;
}
if (c.fieldGroup && (isNil(k) || key.indexOf(`${k}.`) === 0)) {
const field = getField(c, isNil(k) ? key : key.slice(k.length + 1));
if (field) {
return field;
}
}
}
return undefined;
}
function markFieldForCheck(field) {
var _a;
(_a = field._componentRefs) === null || _a === void 0 ? void 0 : _a.forEach((ref) => {
// NOTE: we cannot use ref.changeDetectorRef, see https://github.com/ngx-formly/ngx-formly/issues/2191
if (ref instanceof ComponentRef) {
const changeDetectorRef = ref.injector.get(ChangeDetectorRef);
changeDetectorRef.markForCheck();
}
else {
ref.markForCheck();
}
});
}
/**
* An InjectionToken for registering additional formly config options (types, wrappers ...).
*/
const FORMLY_CONFIG = new InjectionToken('FORMLY_CONFIG');
/**
* Maintains list of formly config options. This can be used to register new field type.
*/
class FormlyConfig {
constructor() {
this.types = {};
this.validators = {};
this.wrappers = {};
this.messages = {};
this.extras = {
checkExpressionOn: 'modelChange',
lazyRender: true,
resetFieldOnHide: true,
renderFormlyFieldElement: true,
showError(field) {
var _a, _b, _c, _d;
return (((_a = field.formControl) === null || _a === void 0 ? void 0 : _a.invalid) &&
(((_b = field.formControl) === null || _b === void 0 ? void 0 : _b.touched) || ((_c = field.options.parentForm) === null || _c === void 0 ? void 0 : _c.submitted) || !!((_d = field.field.validation) === null || _d === void 0 ? void 0 : _d.show)));
},
};
this.extensions = {};
this.presets = {};
this.extensionsByPriority = {};
}
addConfig(config) {
if (config.types) {
config.types.forEach((type) => this.setType(type));
}
if (config.validators) {
config.validators.forEach((validator) => this.setValidator(validator));
}
if (config.wrappers) {
config.wrappers.forEach((wrapper) => this.setWrapper(wrapper));
}
if (config.validationMessages) {
config.validationMessages.forEach((validation) => this.addValidatorMessage(validation.name, validation.message));
}
if (config.extensions) {
this.setSortedExtensions(config.extensions);
}
if (config.extras) {
this.extras = Object.assign(Object.assign({}, this.extras), config.extras);
}
if (config.presets) {
this.presets = Object.assign(Object.assign({}, this.presets), config.presets.reduce((acc, curr) => (Object.assign(Object.assign({}, acc), { [curr.name]: curr.config })), {}));
}
}
/**
* Allows you to specify a custom type which you can use in your field configuration.
* You can pass an object of options, or an array of objects of options.
*/
setType(options) {
if (Array.isArray(options)) {
options.forEach((option) => this.setType(option));
}
else {
if (!this.types[options.name]) {
this.types[options.name] = { name: options.name };
}
['component', 'extends', 'defaultOptions', 'wrappers'].forEach((prop) => {
if (options.hasOwnProperty(prop)) {
this.types[options.name][prop] = options[prop];
}
});
}
}
getType(name, throwIfNotFound = false) {
if (name instanceof Type) {
return { component: name, name: name.prototype.constructor.name };
}
if (!this.types[name]) {
if (throwIfNotFound) {
throw new Error(`[Formly Error] The type "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`);
}
return null;
}
this.mergeExtendedType(name);
return this.types[name];
}
/** @ignore */
getMergedField(field = {}) {
var _a;
const type = this.getType(field.type);
if (!type) {
return;
}
if (type.defaultOptions) {
reverseDeepMerge(field, type.defaultOptions);
}
const extendDefaults = type.extends && this.getType(type.extends).defaultOptions;
if (extendDefaults) {
reverseDeepMerge(field, extendDefaults);
}
if (field === null || field === void 0 ? void 0 : field.optionsTypes) {
field.optionsTypes.forEach((option) => {
const defaultOptions = this.getType(option).defaultOptions;
if (defaultOptions) {
reverseDeepMerge(field, defaultOptions);
}
});
}
const componentRef = this.resolveFieldTypeRef(field);
if ((_a = componentRef === null || componentRef === void 0 ? void 0 : componentRef.instance) === null || _a === void 0 ? void 0 : _a.defaultOptions) {
reverseDeepMerge(field, componentRef.instance.defaultOptions);
}
if (!field.wrappers && type.wrappers) {
field.wrappers = [...type.wrappers];
}
}
/** @ignore @internal */
resolveFieldTypeRef(field = {}) {
const type = this.getType(field.type);
if (!type) {
return null;
}
if (!type.component || type._componentRef) {
return type._componentRef;
}
const { _viewContainerRef, _injector } = field.options;
if (!_viewContainerRef || !_injector) {
return null;
}
const componentRef = _viewContainerRef.createComponent(type.component, { injector: _injector });
defineHiddenProp(type, '_componentRef', componentRef);
try {
componentRef.destroy();
}
catch (e) {
console.error(`An error occurred while destroying the Formly component type "${field.type}"`, e);
}
return type._componentRef;
}
setWrapper(options) {
this.wrappers[options.name] = options;
if (options.types) {
options.types.forEach((type) => {
this.setTypeWrapper(type, options.name);
});
}
}
getWrapper(name) {
if (name instanceof Type) {
return { component: name, name: name.prototype.constructor.name };
}
if (!this.wrappers[name]) {
throw new Error(`[Formly Error] The wrapper "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`);
}
return this.wrappers[name];
}
/** @ignore */
setTypeWrapper(type, name) {
if (!this.types[type]) {
this.types[type] = {};
}
if (!this.types[type].wrappers) {
this.types[type].wrappers = [];
}
if (this.types[type].wrappers.indexOf(name) === -1) {
this.types[type].wrappers.push(name);
}
}
setValidator(options) {
this.validators[options.name] = options;
}
getValidator(name) {
if (!this.validators[name]) {
throw new Error(`[Formly Error] The validator "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`);
}
return this.validators[name];
}
addValidatorMessage(name, message) {
this.messages[name] = message;
if (typeof ngDevMode === 'undefined' || ngDevMode) {
const deprecated = { minlength: 'minLength', maxlength: 'maxLength' };
if (deprecated[name]) {
console.warn(`Formly deprecation: passing validation messages key '${name}' is deprecated since v6.0, use '${deprecated[name]}' instead.`);
this.messages[deprecated[name]] = message;
}
}
}
getValidatorMessage(name) {
return this.messages[name];
}
setSortedExtensions(extensionOptions) {
// insert new extensions, grouped by priority
extensionOptions.forEach((extensionOption) => {
var _a;
const priority = (_a = extensionOption.priority) !== null && _a !== void 0 ? _a : 1;
this.extensionsByPriority[priority] = Object.assign(Object.assign({}, this.extensionsByPriority[priority]), { [extensionOption.name]: extensionOption.extension });
});
// flatten extensions object with sorted keys
this.extensions = Object.keys(this.extensionsByPriority)
.map(Number)
.sort((a, b) => a - b)
.reduce((acc, prio) => (Object.assign(Object.assign({}, acc), this.extensionsByPriority[prio])), {});
}
mergeExtendedType(name) {
if (!this.types[name].extends) {
return;
}
const extendedType = this.getType(this.types[name].extends);
if (!this.types[name].component) {
this.types[name].component = extendedType.component;
}
if (!this.types[name].wrappers) {
this.types[name].wrappers = extendedType.wrappers;
}
}
}
FormlyConfig.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
FormlyConfig.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyConfig, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyConfig, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
class FormlyFormBuilder {
constructor(config, injector, viewContainerRef, parentForm) {
this.config = config;
this.injector = injector;
this.viewContainerRef = viewContainerRef;
this.parentForm = parentForm;
}
buildForm(form, fieldGroup = [], model, options) {
this.build({ fieldGroup, model, form, options });
}
build(field) {
if (!this.config.extensions.core) {
throw new Error('NgxFormly: missing `forRoot()` call. use `forRoot()` when registering the `FormlyModule`.');
}
if (!field.parent) {
this._setOptions(field);
}
disableTreeValidityCall(field.form, () => {
var _a, _b;
this._build(field);
if (!field.parent) {
const options = field.options;
(_a = options.checkExpressions) === null || _a === void 0 ? void 0 : _a.call(options, field, true);
(_b = options._detectChanges) === null || _b === void 0 ? void 0 : _b.call(options, field);
}
});
}
_build(field) {
var _a;
if (!field) {
return;
}
const extensions = Object.values(this.config.extensions);
extensions.forEach((extension) => { var _a; return (_a = extension.prePopulate) === null || _a === void 0 ? void 0 : _a.call(extension, field); });
extensions.forEach((extension) => { var _a; return (_a = extension.onPopulate) === null || _a === void 0 ? void 0 : _a.call(extension, field); });
(_a = field.fieldGroup) === null || _a === void 0 ? void 0 : _a.forEach((f) => this._build(f));
extensions.forEach((extension) => { var _a; return (_a = extension.postPopulate) === null || _a === void 0 ? void 0 : _a.call(extension, field); });
}
_setOptions(field) {
field.form = field.form || new FormGroup({});
field.model = field.model || {};
field.options = field.options || {};
const options = field.options;
if (!options._viewContainerRef) {
defineHiddenProp(options, '_viewContainerRef', this.viewContainerRef);
}
if (!options._injector) {
defineHiddenProp(options, '_injector', this.injector);
}
if (!options.build) {
options._buildForm = () => {
console.warn(`Formly: 'options._buildForm' is deprecated since v6.0, use 'options.build' instead.`);
this.build(field);
};
options.build = (f = field) => {
this.build(f);
return f;
};
}
if (!options.parentForm && this.parentForm) {
defineHiddenProp(options, 'parentForm', this.parentForm);
observe(options, ['parentForm', 'submitted'], ({ firstChange }) => {
if (!firstChange) {
options.detectChanges(field);
}
});
}
}
}
FormlyFormBuilder.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFormBuilder, deps: [{ token: FormlyConfig }, { token: i0.Injector }, { token: i0.ViewContainerRef, optional: true }, { token: i2.FormGroupDirective, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
FormlyFormBuilder.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFormBuilder, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFormBuilder, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: function () {
return [{ type: FormlyConfig }, { type: i0.Injector }, { type: i0.ViewContainerRef, decorators: [{
type: Optional
}] }, { type: i2.FormGroupDirective, decorators: [{
type: Optional
}] }];
} });
function unregisterControl(field, emitEvent = false) {
const control = field.formControl;
const fieldIndex = control._fields ? control._fields.indexOf(field) : -1;
if (fieldIndex !== -1) {
control._fields.splice(fieldIndex, 1);
}
const form = control.parent;
if (!form) {
return;
}
const opts = { emitEvent };
if (form instanceof FormArray) {
const key = form.controls.findIndex((c) => c === control);
if (key !== -1) {
form.removeAt(key, opts);
}
}
else if (form instanceof FormGroup) {
const paths = getKeyPath(field);
const key = paths[paths.length - 1];
if (form.get([key]) === control) {
form.removeControl(key, opts);
}
}
control.setParent(null);
}
function findControl(field) {
var _a;
if (field.formControl) {
return field.formControl;
}
if (field.shareFormControl === false) {
return null;
}
return (_a = field.form) === null || _a === void 0 ? void 0 : _a.get(getKeyPath(field));
}
function registerControl(field, control, emitEvent = false) {
control = control || field.formControl;
if (!control._fields) {
defineHiddenProp(control, '_fields', []);
}
if (control._fields.indexOf(field) === -1) {
control._fields.push(field);
}
if (!field.formControl && control) {
defineHiddenProp(field, 'formControl', control);
control.setValidators(null);
control.setAsyncValidators(null);
field.props.disabled = !!field.props.disabled;
const disabledObserver = observe(field, ['props', 'disabled'], ({ firstChange, currentValue }) => {
if (!firstChange) {
currentValue ? field.formControl.disable() : field.formControl.enable();
}
});
if (control instanceof FormControl) {
control.registerOnDisabledChange(disabledObserver.setValue);
}
}
if (!field.form || !hasKey(field)) {
return;
}
let form = field.form;
const paths = getKeyPath(field);
const value = getFieldValue(field);
if (!(isNil(control.value) && isNil(value)) && control.value !== value && control instanceof FormControl) {
control.patchValue(value);
}
for (let i = 0; i < paths.length - 1; i++) {
const path = paths[i];
if (!form.get([path])) {
form.setControl(path, new FormGroup({}), { emitEvent });
}
form = form.get([path]);
}
const key = paths[paths.length - 1];
if (!field._hide && form.get([key]) !== control) {
form.setControl(key, control, { emitEvent });
}
}
function updateValidity(c, onlySelf = false) {
const status = c.status;
const value = c.value;
c.updateValueAndValidity({ emitEvent: false, onlySelf });
if (status !== c.status) {
c.statusChanges.emit(c.status);
}
if (value !== c.value) {
c.valueChanges.emit(c.value);
}
}
function clearControl(form) {
form === null || form === void 0 ? true : delete form._fields;
form.setValidators(null);
form.setAsyncValidators(null);
if (form instanceof FormGroup || form instanceof FormArray) {
Object.values(form.controls).forEach((c) => clearControl(c));
}
}
class FormlyTemplate {
constructor(ref) {
this.ref = ref;
}
ngOnChanges() {
this.name = this.name || 'formly-group';
}
}
FormlyTemplate.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
FormlyTemplate.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FormlyTemplate, selector: "[formlyTemplate]", inputs: { name: ["formlyTemplate", "name"] }, usesOnChanges: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplate, decorators: [{
type: Directive,
args: [{ selector: '[formlyTemplate]' }]
}], ctorParameters: function () { return [{ type: i0.TemplateRef }]; }, propDecorators: { name: [{
type: Input,
args: ['formlyTemplate']
}] } });
// workarround for https://github.com/angular/angular/issues/43227#issuecomment-904173738
class FormlyFieldTemplates {
}
FormlyFieldTemplates.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
FormlyFieldTemplates.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates, decorators: [{
type: Injectable
}] });
/**
* The `<formly-field>` component is used to render the UI widget (layout + type) of a given `field`.
*/
class FormlyField {
constructor(config, renderer, _elementRef, hostContainerRef, form) {
this.config = config;
this.renderer = renderer;
this._elementRef = _elementRef;
this.hostContainerRef = hostContainerRef;
this.form = form;
this.hostObservers = [];
this.componentRefs = [];
this.hooksObservers = [];
this.detectFieldBuild = false;
this.valueChangesUnsubscribe = () => { };
}
get containerRef() {
return this.config.extras.renderFormlyFieldElement ? this.viewContainerRef : this.hostContainerRef;
}
get elementRef() {
var _a;
if (this.config.extras.renderFormlyFieldElement) {
return this._elementRef;
}
if (((_a = this.componentRefs) === null || _a === void 0 ? void 0 : _a[0]) instanceof ComponentRef) {
return this.componentRefs[0].location;
}
return null;
}
ngAfterContentInit() {
this.triggerHook('afterContentInit');
}
ngAfterViewInit() {
this.triggerHook('afterViewInit');
}
ngDoCheck() {
if (this.detectFieldBuild && this.field && this.field.options) {
this.render();
}
}
ngOnInit() {
this.triggerHook('onInit');
}
ngOnChanges(changes) {
this.triggerHook('onChanges', changes);
}
ngOnDestroy() {
this.resetRefs(this.field);
this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe());
this.hooksObservers.forEach((unsubscribe) => unsubscribe());
this.valueChangesUnsubscribe();
this.triggerHook('onDestroy');
}
renderField(containerRef, f, wrappers = []) {
var _a, _b, _c;
if (this.containerRef === containerRef) {
this.resetRefs(this.field);
this.containerRef.clear();
wrappers = (_a = this.field) === null || _a === void 0 ? void 0 : _a.wrappers;
}
if ((wrappers === null || wrappers === void 0 ? void 0 : wrappers.length) > 0) {
const [wrapper, ...wps] = wrappers;
const { component } = this.config.getWrapper(wrapper);
const ref = containerRef.createComponent(component);
this.attachComponentRef(ref, f);
observe(ref.instance, ['fieldComponent'], ({ currentValue, previousValue, firstChange }) => {
if (currentValue) {
if (previousValue && previousValue._lContainer === currentValue._lContainer) {
return;
}
const viewRef = previousValue ? previousValue.detach() : null;
if (viewRef && !viewRef.destroyed) {
currentValue.insert(viewRef);
}
else {
this.renderField(currentValue, f, wps);
}
!firstChange && ref.changeDetectorRef.detectChanges();
}
});
}
else if (f === null || f === void 0 ? void 0 : f.type) {
const inlineType = (_c = (_b = this.form) === null || _b === void 0 ? void 0 : _b.templates) === null || _c === void 0 ? void 0 : _c.find((ref) => ref.name === f.type);
let ref;
if (inlineType) {
ref = containerRef.createEmbeddedView(inlineType.ref, { $implicit: f });
}
else {
const { component } = this.config.getType(f.type, true);
ref = containerRef.createComponent(component);
}
this.attachComponentRef(ref, f);
}
}
triggerHook(name, changes) {
var _a, _b;
if (name === 'onInit' || (name === 'onChanges' && changes.field && !changes.field.firstChange)) {
this.valueChangesUnsubscribe();
this.valueChangesUnsubscribe = this.fieldChanges(this.field);
}
if ((_b = (_a = this.field) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b[name]) {
if (!changes || changes.field) {
const r = this.field.hooks[name](this.field);
if (isObservable(r) && ['onInit', 'afterContentInit', 'afterViewInit'].indexOf(name) !== -1) {
const sub = r.subscribe();
this.hooksObservers.push(() => sub.unsubscribe());
}
}
}
if (name === 'onChanges' && changes.field) {
this.resetRefs(changes.field.previousValue);
this.render();
}
}
attachComponentRef(ref, field) {
this.componentRefs.push(ref);
field._componentRefs.push(ref);
if (ref instanceof ComponentRef) {
Object.assign(ref.instance, { field });
}
}
render() {
if (!this.field) {
return;
}
// require Formly build
if (!this.field.options) {
this.detectFieldBuild = true;
return;
}
this.detectFieldBuild = false;
this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe());
this.hostObservers = [
observe(this.field, ['hide'], ({ firstChange, currentValue }) => {
const containerRef = this.containerRef;
if (this.config.extras.lazyRender === false) {
firstChange && this.renderField(containerRef, this.field);
if (!firstChange || (firstChange && currentValue)) {
this.elementRef &&
this.renderer.setStyle(this.elementRef.nativeElement, 'display', currentValue ? 'none' : '');
}
}
else {
if (currentValue) {
containerRef.clear();
if (this.field.className) {
this.renderer.removeAttribute(this.elementRef.nativeElement, 'class');
}
}
else {
this.renderField(containerRef, this.field);
if (this.field.className) {
this.renderer.setAttribute(this.elementRef.nativeElement, 'class', this.field.className);
}
}
}
!firstChange && this.field.options.detectChanges(this.field);
}),
observe(this.field, ['className'], ({ firstChange, currentValue }) => {
if ((!firstChange || (firstChange && currentValue)) &&
(!this.config.extras.lazyRender || this.field.hide !== true)) {
this.elementRef && this.renderer.setAttribute(this.elementRef.nativeElement, 'class', currentValue);
}
}),
...['touched', 'pristine', 'status'].map((prop) => observe(this.field, ['formControl', prop], ({ firstChange }) => !firstChange && markFieldForCheck(this.field))),
];
}
resetRefs(field) {
if (field) {
if (field._localFields) {
field._localFields = [];
}
else {
defineHiddenProp(this.field, '_localFields', []);
}
if (field._componentRefs) {
field._componentRefs = field._componentRefs.filter((ref) => this.componentRefs.indexOf(ref) === -1);
}
else {
defineHiddenProp(this.field, '_componentRefs', []);
}
}
this.componentRefs = [];
}
fieldChanges(field) {
if (!field) {
return () => { };
}
const subscribes = [observeDeep(field, ['props'], () => field.options.detectChanges(field))];
if (field.options) {
subscribes.push(observeDeep(field.options, ['formState'], () => field.options.detectChanges(field)));
}
for (const key of Object.keys(field._expressions || {})) {
const expressionObserver = observe(field, ['_expressions', key], ({ currentValue, previousValue }) => {
if (previousValue === null || previousValue === void 0 ? void 0 : previousValue.subscription) {
previousValue.subscription.unsubscribe();
previousValue.subscription = null;
}
if (isObservable(currentValue.value$)) {
currentValue.subscription = currentValue.value$.subscribe();
}
});
subscribes.push(() => {
var _a;
if ((_a = field._expressions[key]) === null || _a === void 0 ? void 0 : _a.subscription) {
field._expressions[key].subscription.unsubscribe();
}
expressionObserver.unsubscribe();
});
}
for (const path of [['focus'], ['template'], ['fieldGroupClassName'], ['validation', 'show']]) {
const fieldObserver = observe(field, path, ({ firstChange }) => !firstChange && field.options.detectChanges(field));
subscribes.push(() => fieldObserver.unsubscribe());
}
if (field.formControl && !field.fieldGroup) {
const control = field.formControl;
let valueChanges = control.valueChanges.pipe(distinctUntilChanged((x, y) => {
if (x !== y || Array.isArray(x) || isObject(x)) {
return false;
}
return true;
}));
if (control.value !== getFieldValue(field)) {
valueChanges = valueChanges.pipe(startWith(control.value));
}
const { updateOn, debounce } = field.modelOptions;
if ((!updateOn || updateOn === 'change') && (debounce === null || debounce === void 0 ? void 0 : debounce.default) > 0) {
valueChanges = control.valueChanges.pipe(debounceTime(debounce.default));
}
const sub = valueChanges.subscribe((value) => {
var _a, _b;
// workaround for https://github.com/angular/angular/issues/13792
if (((_a = control._fields) === null || _a === void 0 ? void 0 : _a.length) > 1 && control instanceof FormControl) {
control.patchValue(value, { emitEvent: false, onlySelf: true });
}
(_b = field.parsers) === null || _b === void 0 ? void 0 : _b.forEach((parserFn) => (value = parserFn(value)));
if (value !== field.formControl.value) {
field.formControl.setValue(value);
return;
}
if (hasKey(field)) {
assignFieldValue(field, value);
}
field.options.fieldChanges.next({ value, field, type: 'valueChanges' });
});
subscribes.push(() => sub.unsubscribe());
}
let templateFieldsSubs = [];
observe(field, ['_localFields'], ({ currentValue }) => {
templateFieldsSubs.forEach((unsubscribe) => unsubscribe());
templateFieldsSubs = (currentValue || []).map((f) => this.fieldChanges(f));
});
return () => {
subscribes.forEach((unsubscribe) => unsubscribe());
templateFieldsSubs.forEach((unsubscribe) => unsubscribe());
};
}
}
FormlyField.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyField, deps: [{ token: FormlyConfig }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.ViewContainerRef }, { token: FormlyFieldTemplates, optional: true }], target: i0.ɵɵFactoryTarget.Component });
FormlyField.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyField, selector: "formly-field", inputs: { field: "field" }, viewQueries: [{ propertyName: "viewContainerRef", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: '<ng-template #container></ng-template>', isInline: true, styles: [":host:empty{display:none}\n"] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyField, decorators: [{
type: Component,
args: [{ selector: 'formly-field', template: '<ng-template #container></ng-template>', styles: [":host:empty{display:none}\n"] }]
}], ctorParameters: function () {
return [{ type: FormlyConfig }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.ViewContainerRef }, { type: FormlyFieldTemplates, decorators: [{
type: Optional
}] }];
}, propDecorators: { field: [{
type: Input
}], viewContainerRef: [{
type: ViewChild,
args: ['container', { read: ViewContainerRef, static: true }]
}] } });
/**
* The `<form-form>` component is the main container of the form,
* which takes care of managing the form state
* and delegates the rendering of each field to `<formly-field>` component.
*/
class FormlyForm {
constructor(builder, config, ngZone, fieldTemplates) {
this.builder = builder;
this.config = config;
this.ngZone = ngZone;
this.fieldTemplates = fieldTemplates;
/** Event that is emitted when the model value is changed */
this.modelChange = new EventEmitter();
this.field = { type: 'formly-group' };
this._modelChangeValue = {};
this.valueChangesUnsubscribe = () => { };
}
/** The form instance which allow to track model value and validation status. */
set form(form) {
this.field.form = form;
}
get form() {
return this.field.form;
}
/** The model to be represented by the form. */
set model(model) {
if (this.config.extras.immutable && this._modelChangeValue === model) {
return;
}
this.setField({ model });
}
get model() {
return this.field.model;
}
/** The field configurations for building the form. */
set fields(fieldGroup) {
this.setField({ fieldGroup });
}
get fields() {
return this.field.fieldGroup;
}
/** Options for the form. */
set options(options) {
this.setField({ options });
}
get options() {
return this.field.options;
}
set templates(templates) {
this.fieldTemplates.templates = templates;
}
ngDoCheck() {
if (this.config.extras.checkExpressionOn === 'changeDetectionCheck') {
this.checkExpressionChange();
}
}
ngOnChanges(changes) {
if (changes.fields && this.form) {
clearControl(this.form);
}
if (changes.fields || changes.form || (changes.model && this._modelChangeValue !== changes.model.currentValue)) {
this.valueChangesUnsubscribe();
this.builder.build(this.field);
this.valueChangesUnsubscribe = this.valueChanges();
}
}
ngOnDestroy() {
this.valueChangesUnsubscribe();
}
checkExpressionChange() {
var _a, _b;
(_b = (_a = this.field.options).checkExpressions) === null || _b === void 0 ? void 0 : _b.call(_a, this.field);
}
valueChanges() {
this.valueChangesUnsubscribe();
const sub = this.field.options.fieldChanges
.pipe(filter(({ field, type }) => hasKey(field) && type === 'valueChanges'), switchMap(() => this.ngZone.onStable.asObservable().pipe(take(1))))
.subscribe(() => this.ngZone.runGuarded(() => {
// runGuarded is used to keep in sync the expression changes
// https://github.com/ngx-formly/ngx-formly/issues/2095
this.checkExpressionChange();
this.modelChange.emit((this._modelChangeValue = clone(this.model)));
}));
return () => sub.unsubscribe();
}
setField(field) {
if (this.config.extras.immutable) {
this.field = Object.assign(Object.assign({}, this.field), clone(field));
}
else {
Object.keys(field).forEach((p) => (this.field[p] = field[p]));
}
}
}
FormlyForm.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyForm, deps: [{ token: FormlyFormBuilder }, { token: FormlyConfig }, { token: i0.NgZone }, { token: FormlyFieldTemplates }], target: i0.ɵɵFactoryTarget.Component });
FormlyForm.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyForm, selector: "formly-form", inputs: { form: "form", model: "model", fields: "fields", options: "options" }, outputs: { modelChange: "modelChange" }, providers: [FormlyFormBuilder, FormlyFieldTemplates], queries: [{ propertyName: "templates", predicate: FormlyTemplate }], usesOnChanges: true, ngImport: i0, template: '<formly-field [field]="field"></formly-field>', isInline: true, components: [{ type: FormlyField, selector: "formly-field", inputs: ["field"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyForm, decorators: [{
type: Component,
args: [{
selector: 'formly-form',
template: '<formly-field [field]="field"></formly-field>',
providers: [FormlyFormBuilder, FormlyFieldTemplates],
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}], ctorParameters: function () { return [{ type: FormlyFormBuilder }, { type: FormlyConfig }, { type: i0.NgZone }, { type: FormlyFieldTemplates }]; }, propDecorators: { form: [{
type: Input
}], model: [{
type: Input
}], fields: [{
type: Input
}], options: [{
type: Input
}], modelChange: [{
type: Output
}], templates: [{
type: ContentChildren,
args: [FormlyTemplate]
}] } });
/**
* Allow to link the `field` HTML attributes (`id`, `name` ...) and Event attributes (`focus`, `blur` ...) to an element in the DOM.
*/
class FormlyAttributes {
constructor(renderer, elementRef, _document) {
this.renderer = renderer;
this.elementRef = elementRef;
this.uiAttributesCache = {};
/**
* HostBinding doesn't register listeners conditionally which may produce some perf issues.
*
* Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1991
*/
this.uiEvents = {
listeners: [],
events: ['click', 'keyup', 'keydown', 'keypress', 'focus', 'blur', 'change'],
callback: (eventName, $event) => {
switch (eventName) {
case 'focus':
return this.onFocus($event);
case 'blur':
return this.onBlur($event);
case 'change':
return this.onChange($event);
default:
return this.props[eventName](this.field, $event);
}
},
};
this.document = _document;
}
get props() {
return this.field.props || {};
}
get fieldAttrElements() {
var _a;
return ((_a = this.field) === null || _a === void 0 ? void 0 : _a['_elementRefs']) || [];
}
ngOnChanges(changes) {
var _a;
if (changes.field) {
this.field.name && this.setAttribute('name', this.field.name);
this.uiEvents.listeners.forEach((listener) => listener());
this.uiEvents.events.forEach((eventName) => {
var _a;
if (((_a = this.props) === null || _a === void 0 ? void 0 : _a[eventName]) || ['focus', 'blur', 'change'].indexOf(eventName) !== -1) {
this.uiEvents.listeners.push(this.renderer.listen(this.elementRef.nativeElement, eventName, (e) => this.uiEvents.callback(eventName, e)));
}
});
if ((_a = this.props) === null || _a === void 0 ? void 0 : _a.attributes) {
observe(this.field, ['props', 'attributes'], ({ currentValue, previousValue }) => {
if (previousValue) {
Object.keys(previousValue).forEach((attr) => this.removeAttribute(attr));
}
if (currentValue) {
Object.keys(currentValue).forEach((attr) => {
if (currentValue[attr] != null) {
this.setAttribute(attr, currentValue[attr]);
}
});
}
});
}
this.detachElementRef(changes.field.previousValue);
this.attachElementRef(changes.field.currentValue);
if (this.fieldAttrElements.length === 1) {
!this.id && this.field.id && this.setAttribute('id', this.field.id);
this.focusObserver = observe(this.field, ['focus'], ({ currentValue }) => {
this.toggleFocus(currentValue);
});
}
}