@fluent-form/core
Version:
An Angular dynamic forms library powered by Fluent API and JSON.
1,020 lines (993 loc) • 127 kB
JavaScript
import * as i0 from '@angular/core';
import { TemplateRef, ViewChild, Directive, InjectionToken, Injectable, inject, createComponent, Injector, EnvironmentInjector, input, model, computed, untracked, output, DestroyRef, effect, ChangeDetectionStrategy, Component, ElementRef, reflectComponentType, isSignal, runInInjectionContext, Input, ViewEncapsulation, booleanAttribute, NgModule, forwardRef, HostListener, ViewContainerRef, Pipe, APP_BOOTSTRAP_LISTENER, ApplicationRef, makeEnvironmentProviders } from '@angular/core';
import * as i1 from '@angular/common';
import { NgTemplateOutlet, AsyncPipe } from '@angular/common';
import { takeUntilDestroyed, outputToObservable, toSignal } from '@angular/core/rxjs-interop';
import { Validators, FormControl, FormGroup, FormArray } from '@angular/forms';
import { signalSetFn, SIGNAL } from '@angular/core/primitives/signals';
import { Subject, takeUntil, Observable, fromEvent, map } from 'rxjs';
import { _CdkPrivateStyleLoader } from '@angular/cdk/private';
import { BreakpointObserver } from '@angular/cdk/layout';
class TemplateRefHolder {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TemplateRefHolder, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: TemplateRefHolder, isStandalone: true, viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true, static: true }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TemplateRefHolder, decorators: [{
type: Directive
}], propDecorators: { templateRef: [{
type: ViewChild,
args: [TemplateRef, { static: true }]
}] } });
class AbstractFormContentComponent extends TemplateRefHolder {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AbstractFormContentComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: AbstractFormContentComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AbstractFormContentComponent, decorators: [{
type: Directive
}] });
const WIDGET_MAP = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'WIDGET_MAP' : '');
const SCHEMA_MAP = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'SCHEMA_MAP' : '');
const NAMED_TEMPLATES = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'NAMED_TEMPLATES' : '');
const FLUENT_FORM_CONTENT = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FLUENT_FORM_CONTENT' : '');
const FLUENT_WIDGET_WRAPPERS = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FLUENT_WIDGET_WRAPPERS' : '');
const isObject = (o) => typeof o === 'object';
const isNumber = (o) => typeof o === 'number';
const isString = (o) => typeof o === 'string';
const isFunction = (o) => typeof o === 'function';
const isBoolean = (o) => typeof o === 'boolean';
const isUndefined = (o) => o === undefined;
const isArray = (o) => Array.isArray(o);
const RETURN_STR = 'return ';
const STATIC_EXPRESSION_PATTERN = /^{{.+}}$/;
const INTERPOLATION_PATTERN = /^{{|}}$/g;
class CodeEvaluator {
}
function isStaticExpression(str) {
return STATIC_EXPRESSION_PATTERN.test(str);
}
class DynamicCodeEvaluator {
evaluate(code, context) {
code = RETURN_STR + code.replace(INTERPOLATION_PATTERN, '');
// TODO: This doesn't work with `unsafe-eval` enabled in CSP.
const fn = new Function('o', `with(o){${code}}`);
const proxy = new Proxy(Object.freeze(context), {
// Intercept all properties to prevent lookup beyond the `Proxy` object's scope chain.
has() {
return true;
},
get(target, key, receiver) {
if (key === Symbol.unscopables) {
return undefined;
}
return Reflect.get(target, key, receiver);
}
});
return fn(proxy);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DynamicCodeEvaluator, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DynamicCodeEvaluator, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DynamicCodeEvaluator, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* @internal
*/
class ValueTransformer {
constructor() {
this.evaluator = inject(CodeEvaluator, { optional: true });
}
transform(value, context = {}) {
if (isFunction(value)) {
return value(context);
}
if (isString(value) && this.evaluator && isStaticExpression(value)) {
return this.evaluator.evaluate(value, context);
}
return value;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ValueTransformer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ValueTransformer, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ValueTransformer, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
function throwWidgetNotFoundError(name) {
throw new Error(`The '${name}' widget was not found`);
}
function throwCustomTemplateNotFoundError(name) {
throw new Error(`The custom '${name}' template was not found`);
}
const SCHEMA_PATCHERS = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'SchemaPatchers' : '');
function provideSchemaPatcher(patcher) {
return {
provide: SCHEMA_PATCHERS,
useValue: patcher,
multi: true
};
}
/**
* @public
*/
var SchemaType;
(function (SchemaType) {
SchemaType[SchemaType["Control"] = 0] = "Control";
SchemaType[SchemaType["ControlGroup"] = 1] = "ControlGroup";
SchemaType[SchemaType["ControlArray"] = 2] = "ControlArray";
SchemaType[SchemaType["ControlWrapper"] = 3] = "ControlWrapper";
SchemaType[SchemaType["Component"] = 4] = "Component";
SchemaType[SchemaType["ComponentContainer"] = 5] = "ComponentContainer";
SchemaType[SchemaType["ComponentWrapper"] = 6] = "ComponentWrapper";
})(SchemaType || (SchemaType = {}));
var SchemaKind;
(function (SchemaKind) {
SchemaKind["Headless"] = "headless";
SchemaKind["Headful"] = "headful";
SchemaKind["Template"] = "template";
SchemaKind["Row"] = "row";
})(SchemaKind || (SchemaKind = {}));
const ANY_SCHEMA_SELECTOR = '*';
class SchemaUtil {
constructor() {
this.schemaMap = inject(SCHEMA_MAP);
this.schemaPatchers = inject(SCHEMA_PATCHERS, { optional: true }) ?? [];
}
patch(schema) {
if (this.isControlWrapper(schema) ||
this.isComponentWrapper(schema) ||
this.isControlContainer(schema) ||
this.isComponentContainer(schema)) {
schema.schemas = schema.schemas.map(schema => this.patch(schema));
}
const config = this.schemaMap.get(schema.kind);
if (typeof ngDevMode !== 'undefined' && ngDevMode && !config) {
throwWidgetNotFoundError(schema.kind);
}
const { type } = config;
return this.schemaPatchers
.filter(patcher => {
// Convert selector to an array for unified processing.
const selector = isArray(patcher.selector) ? patcher.selector : [patcher.selector];
return selector.some(kindOrType => {
if (isString(kindOrType)) {
return kindOrType === ANY_SCHEMA_SELECTOR || kindOrType === schema.kind;
}
return kindOrType === type;
});
})
.reduce((patchedSchema, patcher) => {
return patcher.patch(patchedSchema);
}, schema);
}
/**
* Filter out top-level control/control container schemas.
*/
filterControls(schemas) {
return schemas.reduce((schemas, schema) => {
if (this.isControlWrapper(schema) || this.isComponentContainer(schema)) {
schemas = schemas.concat(this.filterControls(schema.schemas));
}
else if (this.isControl(schema) || this.isControlContainer(schema)) {
schemas.push(schema);
}
return schemas;
}, []);
}
isControlGroup(schema) {
return this.typeOf(schema) === SchemaType.ControlGroup;
}
isControlArray(schema) {
return this.typeOf(schema) === SchemaType.ControlArray;
}
isControlContainer(schema) {
return this.isControlGroup(schema) || this.isControlArray(schema);
}
isControlWrapper(schema) {
return this.typeOf(schema) === SchemaType.ControlWrapper;
}
isControl(schema) {
return this.typeOf(schema) === SchemaType.Control;
}
isComponentContainer(schema) {
return this.typeOf(schema) === SchemaType.ComponentContainer;
}
isComponentWrapper(schema) {
return this.typeOf(schema) === SchemaType.ComponentWrapper;
}
isComponent(schema) {
return this.typeOf(schema) === SchemaType.Component;
}
/**
* Whether it is a multi-field schema.
*/
isMultiKeySchema(schema) {
return isArray(schema.key);
}
/**
* Whether it is a path field schema.
*/
isPathKeySchema(schema) {
return isString(schema.key) && schema.key.includes('.');
}
/**
* Non-control schema, indicating that it or its child nodes
* do not contain any control schemas.
*/
isNonControl(schema) {
return this.isComponent(schema) || this.isComponentWrapper(schema);
}
typeOf(schema) {
return this.schemaMap.get(schema.kind)?.type;
}
validatorsOf(schema) {
const validators = this.schemaMap.get(schema.kind)?.validators?.(schema) ?? [];
if (schema.required === true) {
validators.push(Validators.required);
}
return validators;
}
parsePathKey(key) {
return key.split('.');
}
norimalizePaths(paths) {
return (isArray(paths) ? paths : [paths]).map(o => o.toString());
}
find(schema, paths) {
let currentSchema = schema;
for (const path of this.norimalizePaths(paths)) {
currentSchema = currentSchema['schemas'].find((o) => o.key?.toString() === path) ?? null;
if (currentSchema === null) {
return currentSchema;
}
}
return currentSchema;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SchemaUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SchemaUtil, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: SchemaUtil, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/**
* @internal
*/
class ValueUtil {
constructor() {
this.schemaUtil = inject(SchemaUtil);
}
valueOfModel(model, schema) {
let value;
// If the value read from the model is undefined, it means the model did
// not provide a value, so take the default value from the schema here.
// If it is a multi-key schema, the values of these keys need to be retrieved
// separately from the model and combined into an array.
if (this.schemaUtil.isMultiKeySchema(schema)) {
value = schema.key.map((key, index) => {
const val = model[key];
return isUndefined(val) ? schema.defaultValue?.[index] ?? null : val;
});
// If all elements in the array are `null`, assign `null` directly.
if (value.every(o => o === null)) {
value = null;
}
}
else if (this.schemaUtil.isPathKeySchema(schema)) {
const paths = this.schemaUtil.parsePathKey(schema.key);
value = paths.reduce((obj, key) => obj?.[key], model);
}
else {
value = model[schema.key];
}
if (isUndefined(value)) {
value = schema.defaultValue ?? null;
}
if (schema.mapper) {
return schema.mapper.parser(value, schema);
}
return value;
}
valueOfControl(control, schema) {
const value = control.value;
if (schema.mapper) {
return schema.mapper.formatter(value, schema);
}
return value;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ValueUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ValueUtil, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ValueUtil, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
class FormUtil {
constructor() {
this.schemaUtil = inject(SchemaUtil);
this.valueUtil = inject(ValueUtil);
this.valueTransformer = inject(ValueTransformer);
}
createFormControl(schema, model) {
const validators = this.schemaUtil.validatorsOf(schema);
const modalValue = this.valueUtil.valueOfModel(model, schema);
const control = new FormControl(schema.defaultValue ?? null, {
nonNullable: !isUndefined(schema.defaultValue),
validators: schema.validators ? validators.concat(schema.validators) : validators,
asyncValidators: schema.asyncValidators,
updateOn: schema.config?.updateOn
});
if (modalValue !== null) {
control.setValue(modalValue, { emitEvent: false });
}
return control;
}
createFormGroup(schemaOrSchemas, model) {
let schemas;
let options = {};
if (isArray(schemaOrSchemas)) {
schemas = schemaOrSchemas;
}
else {
schemas = schemaOrSchemas.schemas;
options = {
validators: schemaOrSchemas.validators,
asyncValidators: schemaOrSchemas.asyncValidators,
updateOn: schemaOrSchemas.config?.updateOn
};
}
return new FormGroup(this.createControlMap(schemas, model), options);
}
createControlMap(schemas, model) {
return schemas.reduce((controls, schema) => {
if (this.schemaUtil.isControlGroup(schema)) {
const key = schema.key.toString();
controls[key] = this.createFormGroup(schema, model[key] ?? {});
}
else if (this.schemaUtil.isControlArray(schema)) {
const key = schema.key.toString();
controls[key] = this.createFormArray(schema, model[key] ?? []);
}
else if (this.schemaUtil.isControlWrapper(schema) || this.schemaUtil.isComponentContainer(schema)) {
Object.assign(controls, this.createControlMap(schema.schemas, model));
}
else if (this.schemaUtil.isControl(schema)) {
if (this.schemaUtil.isPathKeySchema(schema)) {
const paths = this.schemaUtil.parsePathKey(schema.key);
const key = paths.pop();
const parent = paths.reduce((previousGroup, path) => {
let group = previousGroup.get(path);
if (!group) {
group = new FormGroup({});
previousGroup.addControl(path, group);
}
return group;
}, (controls[paths.shift()] ??= new FormGroup({})));
parent.addControl(key, this.createFormControl(schema, model));
}
else {
controls[schema.key.toString()] = this.createFormControl(schema, model);
}
}
return controls;
}, {});
}
createFormArray(schema, model) {
const controls = this.createFormArrayElements(schema.schemas, model);
const validators = schema.validators ?? [];
if (schema.length) {
validators.push(Validators.required);
if (isNumber(schema.length)) {
validators.push(Validators.minLength(schema.length), Validators.maxLength(schema.length));
}
else {
if (schema.length.min) {
validators.push(Validators.minLength(schema.length.min));
}
if (schema.length.max) {
validators.push(Validators.maxLength(schema.length.max));
}
}
}
return new FormArray(controls, {
validators,
asyncValidators: schema.asyncValidators,
updateOn: schema.config?.updateOn
});
}
createFormArrayElements(schemas, model) {
// Only take the first one, ignore the rest.
const [schema] = this.schemaUtil.filterControls(schemas);
if (typeof ngDevMode !== 'undefined' && ngDevMode && !schema) {
throw Error('FormArray element control schema not found');
}
return model.map((_, index) => {
return this.createAnyControl({ ...schema, key: index }, model);
});
}
createAnyControl(schema, model) {
if (this.schemaUtil.isControlGroup(schema)) {
return this.createFormGroup(schema, model[schema.key] ?? {});
}
if (this.schemaUtil.isControlArray(schema)) {
return this.createFormArray(schema, model[schema.key] ?? []);
}
return this.createFormControl(schema, model);
}
updateForm(form, model, schemas) {
for (const schema of schemas) {
if (this.schemaUtil.isControlGroup(schema)) {
const key = schema.key;
const formGroup = getChildControl(form, key);
this.updateForm(formGroup, model[key], schema.schemas);
continue;
}
if (this.schemaUtil.isControlArray(schema)) {
const key = schema.key;
const formArray = getChildControl(form, key);
const [elementSchema] = this.schemaUtil.filterControls(schema.schemas);
const elementSchemas = formArray.controls.map((_, index) => ({ ...elementSchema, key: index }));
this.updateForm(formArray, model[schema.key], elementSchemas);
continue;
}
if (this.schemaUtil.isControlWrapper(schema) || this.schemaUtil.isComponentContainer(schema)) {
this.updateForm(form, model, schema.schemas);
continue;
}
if (this.schemaUtil.isControl(schema)) {
const control = getChildControl(form, schema.key);
if (schema.value) {
const value = this.valueTransformer.transform(schema.value, { model, schema, control });
control.setValue(value, { onlySelf: true });
}
// update disabled
const disabled = this.valueTransformer.transform(schema.disabled, { model, schema, control });
if (control.enabled !== !disabled) { // Update only if inconsistent
if (disabled) {
// Using `onlySelf: true` here instead of `emitEvent: false` because the
// control's own value/statusChanges events need to trigger properly.
control.disable({ onlySelf: true });
}
else {
control.enable({ onlySelf: true });
}
}
// update required validator
const required = this.valueTransformer.transform(schema.required, { model, schema, control });
if (required) {
control.addValidators(Validators.required);
}
else {
control.removeValidators(Validators.required);
}
control.updateValueAndValidity({ emitEvent: false });
}
}
}
updateModel(model, form, schemas) {
for (const schema of schemas) {
if (this.schemaUtil.isControlGroup(schema)) {
const key = schema.key;
const formGroup = getChildControl(form, key);
this.updateModel(model[key] = {}, formGroup, schema.schemas);
continue;
}
if (this.schemaUtil.isControlArray(schema)) {
const key = schema.key;
const formArray = getChildControl(form, key);
const [elementSchema] = this.schemaUtil.filterControls(schema.schemas);
const elementSchemas = formArray.controls.map((_, index) => ({ ...elementSchema, key: index }));
this.updateModel(model[key] = [], formArray, elementSchemas);
continue;
}
if (this.schemaUtil.isControlWrapper(schema) || this.schemaUtil.isComponentContainer(schema)) {
this.updateModel(model, form, schema.schemas);
continue;
}
if (this.schemaUtil.isControl(schema)) {
const key = schema.key.toString();
const control = getChildControl(form, key);
const value = this.valueUtil.valueOfControl(control, schema);
// Multi-field case.
if (this.schemaUtil.isMultiKeySchema(schema)) {
schema.key.map((prop, idx) => {
model[prop] = value?.[idx] ?? null;
});
}
else if (this.schemaUtil.isPathKeySchema(schema)) {
const paths = this.schemaUtil.parsePathKey(schema.key);
let _model = model;
for (let i = 0; i < paths.length - 1; i++) {
const path = paths[i];
_model = _model[path] ??= {};
}
_model[paths.pop()] = value;
}
else {
model[key] = value;
}
}
}
return model;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FormUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FormUtil, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FormUtil, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/**
* Automatically choose to use `.get()` or `.at()` to get a child control
* based on the type of the form.
* @param form
* @param key
* @returns
*/
function getChildControl(form, key) {
return form instanceof FormArray ? form.at(key) : form.get(key.toString());
}
/**
* @internal
*/
class ModelUtil {
constructor() {
this.schemaUtil = inject(SchemaUtil);
this.valueUtil = inject(ValueUtil);
this.formUtil = inject(FormUtil);
}
updateForm(form, model, schemas, completed = true) {
for (const schema of schemas) {
if (this.schemaUtil.isControlGroup(schema)) {
const key = schema.key;
const formGroup = getChildControl(form, key);
this.updateForm(formGroup, model[key] ??= {}, schema.schemas, false);
continue;
}
if (this.schemaUtil.isControlArray(schema)) {
const key = schema.key;
const array = model[key] ??= [];
const formArray = getChildControl(form, key);
// / If the model array length matches the form array length, update the
// form values in place; otherwise, rebuild the form array.
if (array.length === formArray.length) {
const [elementSchema] = this.schemaUtil.filterControls(schema.schemas);
const elementSchemas = array.map((_, index) => ({ ...elementSchema, key: index }));
this.updateForm(formArray, array, elementSchemas, false);
}
else {
const controls = this.formUtil.createFormArrayElements(schema.schemas, array);
formArray.clear({ emitEvent: false });
for (const control of controls) {
formArray.push(control, { emitEvent: false });
}
formArray.updateValueAndValidity({ onlySelf: true });
}
continue;
}
if (this.schemaUtil.isComponentContainer(schema) || this.schemaUtil.isControlWrapper(schema)) {
this.updateForm(form, model, schema.schemas, false);
continue;
}
if (this.schemaUtil.isControl(schema)) {
const value = this.valueUtil.valueOfModel(model, schema);
const control = getChildControl(form, schema.key);
control.setValue(value, { onlySelf: true });
}
}
// Only disable `onlySelf` after all child updates are complete.
form.updateValueAndValidity({ onlySelf: !completed });
return form;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ModelUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ModelUtil, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ModelUtil, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
class FluentForm {
constructor() {
this.formUtil = inject(FormUtil);
this.modelUtil = inject(ModelUtil);
this.schemaUtil = inject(SchemaUtil);
this.formContentRef = createComponent(inject(FLUENT_FORM_CONTENT), {
environmentInjector: inject(EnvironmentInjector),
elementInjector: inject(Injector)
});
this.schema = input.required(...(ngDevMode ? [{ debugName: "schema" }] : []));
this.model = model.required(...(ngDevMode ? [{ debugName: "model" }] : []));
this.patchedSchema = computed(() => this.schemaUtil.patch(this.schema()), ...(ngDevMode ? [{ debugName: "patchedSchema" }] : []));
this.form = computed(() => this.formUtil.createFormGroup(this.patchedSchema(), untracked(this.model)), ...(ngDevMode ? [{ debugName: "form" }] : []));
this.formChange = output();
this.valueChanges = output();
this.statusChanges = output();
// eslint-disable-next-line @angular-eslint/no-output-native
this.submit = output();
this.onSubmit = (event) => {
event.stopPropagation();
this.submit.emit(event);
// Forms with `method="dialog"` have some special behavior that won't reload the page and that
// shouldn't be prevented. Note that we need to null check the `event` and the `target`, because
// some internal apps call this method directly with the wrong arguments.
return event?.target?.method === 'dialog';
};
const destroyRef = inject(DestroyRef);
effect(() => {
const form = this.form();
untracked(() => {
this.formChange.emit(form);
this.onValueChanges();
form.valueChanges.pipe(takeUntilDestroyed(destroyRef)).subscribe(value => {
this.onValueChanges();
this.valueChanges.emit(value);
});
form.statusChanges.pipe(takeUntilDestroyed(destroyRef)).subscribe(status => this.statusChanges.emit(status));
});
});
let modelChangeCounter = 0;
effect(() => {
const model = this.model();
untracked(() => {
// If the change comes from outside, assign the value to the form.
if (modelChangeCounter++ > 0 && model !== this.internalModel) {
this.modelUtil.updateForm(this.form(), model, this.patchedSchema().schemas);
}
});
});
destroyRef.onDestroy(() => this.formContentRef.destroy());
}
onValueChanges() {
const form = this.form();
const { schemas } = this.patchedSchema();
this.formUtil.updateForm(form, this.internalModel = this.formUtil.updateModel({}, form, schemas), schemas);
this.model.set(this.internalModel);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FluentForm, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: FluentForm, isStandalone: true, selector: "fluent-form", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { model: "modelChange", formChange: "formChange", valueChanges: "valueChanges", statusChanges: "statusChanges", submit: "submit" }, providers: [
{
provide: NAMED_TEMPLATES,
useFactory: () => []
}
], ngImport: i0, template: "<ng-container\n [ngTemplateOutlet]=\"formContentRef.instance.templateRef\"\n [ngTemplateOutletContext]=\"{ schema: patchedSchema(), model: model(), form: form(), onSubmit }\" />\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FluentForm, decorators: [{
type: Component,
args: [{ selector: 'fluent-form', imports: [NgTemplateOutlet], providers: [
{
provide: NAMED_TEMPLATES,
useFactory: () => []
}
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container\n [ngTemplateOutlet]=\"formContentRef.instance.templateRef\"\n [ngTemplateOutletContext]=\"{ schema: patchedSchema(), model: model(), form: form(), onSubmit }\" />\n" }]
}], ctorParameters: () => [], propDecorators: { schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: true }] }, { type: i0.Output, args: ["modelChange"] }], formChange: [{ type: i0.Output, args: ["formChange"] }], valueChanges: [{ type: i0.Output, args: ["valueChanges"] }], statusChanges: [{ type: i0.Output, args: ["statusChanges"] }], submit: [{ type: i0.Output, args: ["submit"] }] } });
/**
* @deprecated
* This component will be removed in future versions. Please use {@link FluentForm} instead.
*/
const FluentFormComponent = FluentForm;
class AbstractWidgetWrapper extends TemplateRefHolder {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AbstractWidgetWrapper, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: AbstractWidgetWrapper, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AbstractWidgetWrapper, decorators: [{
type: Directive
}] });
const COMPOSE_KEY = 'schemas';
const SCHEMA_STACK = [];
function getCurrentSchema() {
return SCHEMA_STACK[SCHEMA_STACK.length - 1];
}
function enterSchema(schema) {
SCHEMA_STACK.push(schema);
}
function leaveSchema() {
SCHEMA_STACK.pop();
}
function joinSchema(schema) {
const currentSchema = getCurrentSchema();
// If a schema already exists, just push it in as a subschema.
currentSchema?.schemas.push(schema);
}
const IS_BUILDER = Symbol();
const BUILD_KEY = 'build';
function composeBuilder(target = {}, append = true) {
append && joinSchema(target);
const builder = new Proxy(target, {
get(target, property) {
switch (property) {
case IS_BUILDER: return true;
case BUILD_KEY: return () => target;
case COMPOSE_KEY:
return (composeFn) => {
target[COMPOSE_KEY] = [];
enterSchema(target);
try {
composeFn(builder);
}
finally {
leaveSchema();
}
return builder;
};
}
return (value) => {
if (value !== target[property]) {
target[property] = value;
}
return builder;
};
}
});
return builder;
}
/**
* Determines whether the value is a builder.
*/
function isBuilder(value) {
return value[IS_BUILDER] ?? false;
}
function row(key) {
return composeBuilder().kind('row').key(key);
}
function headless(key) {
return composeBuilder().kind('headless').key(key);
}
function form(schemasOrComposeFn) {
if (isArray(schemasOrComposeFn)) {
return {
kind: 'group',
key: 'root',
schemas: schemasOrComposeFn
};
}
return computed(() => composeBuilder()
.kind('group')
.key('root')
.schemas(schemasOrComposeFn)
.build());
}
class DestroyedSubject extends Subject {
constructor() {
super();
inject(DestroyRef).onDestroy(() => {
this.next();
this.complete();
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DestroyedSubject, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DestroyedSubject }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DestroyedSubject, decorators: [{
type: Injectable
}], ctorParameters: () => [] });
class WidgetTemplateRegistry extends Map {
constructor() {
super(...arguments);
this.envInjector = inject(EnvironmentInjector);
this.widgetMap = inject(WIDGET_MAP);
}
get(kind) {
return super.get(kind) ?? this.register(kind);
}
register(kind) {
const component = this.widgetMap.get(kind);
if (typeof ngDevMode !== 'undefined' && ngDevMode && !component) {
throwWidgetNotFoundError(kind);
}
const tmpl = component().then(comp => {
const { instance } = createComponent(comp, {
environmentInjector: this.envInjector
});
return instance.templateRef;
});
this.set(kind, tmpl);
return tmpl;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WidgetTemplateRegistry, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WidgetTemplateRegistry, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WidgetTemplateRegistry, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
class WidgetWrapperTemplateRegistry extends Map {
constructor() {
super(...arguments);
this.envInjector = inject(EnvironmentInjector);
}
get(type) {
return super.get(type) ?? this.register(type);
}
register(type) {
const { templateRef } = createComponent(type, {
environmentInjector: this.envInjector
}).instance;
this.set(type, templateRef);
return templateRef;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WidgetWrapperTemplateRegistry, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WidgetWrapperTemplateRegistry, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WidgetWrapperTemplateRegistry, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
function isHookHolder(value) {
return 'hooks' in value;
}
function isListenerHolder(value) {
return 'listeners' in value;
}
function isPropertyHolder(value) {
return 'properties' in value;
}
function isObserverHolder(value) {
return 'observers' in value;
}
/**
* @internal
*/
class FluentBindingDirective {
constructor() {
this.destroyRef = inject(DestroyRef);
this.injector = inject(Injector);
this.fluentBinding = input.required(...(ngDevMode ? [{ debugName: "fluentBinding" }] : []));
this.schema = computed(() => this.fluentBinding().schema, ...(ngDevMode ? [{ debugName: "schema" }] : []));
this.model = computed(() => this.fluentBinding().model, ...(ngDevMode ? [{ debugName: "model" }] : []));
this.control = computed(() => this.fluentBinding().control, ...(ngDevMode ? [{ debugName: "control" }] : []));
this.component = computed(() => this.fluentBinding().component, ...(ngDevMode ? [{ debugName: "component" }] : []));
const elementRef = inject(ElementRef);
const destroyed = inject(DestroyedSubject);
effect(() => {
const component = this.component();
const schema = this.schema();
const control = this.control();
const element = elementRef.nativeElement;
const outputs = component
? reflectComponentType(component.constructor)?.outputs.map(output => output.propName) ?? []
: [];
untracked(() => {
const context = () => ({ control, schema, model: this.model(), element, component });
destroyed.next();
if (isPropertyHolder(schema)) {
const host = component ?? element;
for (const [property, value] of Object.entries(schema.properties)) {
const prop = host[property];
if (isSignal(prop)) {
signalSetFn(prop[SIGNAL], value);
}
else {
host[property] = value;
}
}
}
if (isListenerHolder(schema)) {
for (const [eventName, listener] of Object.entries(schema.listeners)) {
if (eventName === 'valueChange') {
control.valueChanges.pipe(takeUntil(destroyed)).subscribe(value => {
listener(value, context());
});
}
else if (eventName === 'statusChange') {
control.statusChanges.pipe(takeUntil(destroyed)).subscribe(status => {
listener(status, context());
});
}
else {
// First try to bind to the component's output, then fallback to DOM event binding
if (component) {
if (outputs.includes(eventName)) {
const output = component[eventName];
const observable = output instanceof Observable ? output : outputToObservable(output);
observable.pipe(takeUntil(destroyed)).subscribe(event => {
listener(event, context());
});
continue;
}
}
fromEvent(element, eventName).pipe(takeUntil(destroyed)).subscribe(event => {
listener(event, context());
});
}
}
}
if (isObserverHolder(schema)) {
for (const [eventName, observer] of Object.entries(schema.observers)) {
if (eventName === 'valueChange') {
control.valueChanges.pipe(map(event => ({ event, context: context() })), observer, takeUntil(destroyed)).subscribe();
}
else if (eventName === 'statusChange') {
control.statusChanges.pipe(map(event => ({ event, context: context() })), observer, takeUntil(destroyed)).subscribe();
}
else {
// First try to bind to the component's output, then fallback to DOM event binding
if (component) {
if (outputs.includes(eventName)) {
const output = component[eventName];
const observable = output instanceof Observable ? output : outputToObservable(output);
observable.pipe(map(event => ({ event, context: context() })), observer, takeUntil(destroyed)).subscribe();
continue;
}
}
fromEvent(element, eventName).pipe(map(event => ({ event, context: context() })), observer, takeUntil(destroyed)).subscribe();
}
}
}
});
});
}
ngOnInit() {
const schema = this.schema();
const control = this.control();
const context = () => ({ control, schema, model: this.model() });
if (isHookHolder(schema)) {
runInInjectionContext(this.injector, () => schema.hooks.onInit?.(context()));
this.destroyRef.onDestroy(() => runInInjectionContext(this.injector, () => schema.hooks.onDestroy?.(context())));
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FluentBindingDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.16", type: FluentBindingDirective, isStandalone: true, selector: "[fluentBinding]", inputs: { fluentBinding: { classPropertyName: "fluentBinding", publicName: "fluentBinding", isSignal: true, isRequired: true, transformFunction: null } }, providers: [DestroyedSubject], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FluentBindingDirective, decorators: [{
type: Directive,
args: [{
selector: '[fluentBinding]',
providers: [DestroyedSubject]
}]
}], ctorParameters: () => [], propDecorators: { fluentBinding: [{ type: i0.Input, args: [{ isSignal: true, alias: "fluentBinding", required: true }] }] } });
/**
* @internal
*/
class FluentContextGuardDirective {
/**
* @internal
*/
static ngTemplateContextGuard(_, context) {
return true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FluentContextGuardDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: FluentContextGuardDirective, isStandalone: true, selector: "ng-template[fluentContextGuard]", inputs: { fluentContextGuard: "fluentContextGuard" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FluentContextGuardDirective, decorators: [{
type: Directive,
args: [{
selector: 'ng-template[fluentContextGuard]'
}]
}], propDecorators: { fluentContextGuard: [{
type: Input
}] } });
class FluentControlWrapperDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FluentControlWrapperDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: FluentControlWrapperDirective, isStandalone: true, selector: "[fluentControlWrapper]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FluentControlWrapperDirective, decorators: [{
type: Directive,
args: [{
selector: '[fluentControlWrapper]'
}]
}] });
const Breakpoints = {
xs: '(max-width:575px)',
sm: '(min-width:576px)',
md: '(min-width:768px)',
lg: '(min-width:992px)',
xl: '(min-width:1200px)',
xxl: '(min-width:1400px)'
};
function createBreakpointInfix(breakpoint) {
return breakpoint !== 'xs' ? `-${breakpoint}` : '';
}
class FluentColComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FluentColComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: FluentColComponent, isStandalone: true, selector: "fluent-col", ngImport: i0, template: '', isInline: true, styles: [".fluent-column{position:relative;flex:0 0 auto;min-width:auto}.fluent-column-fill{flex:1 1 auto}.fluent-column-offset-auto{margin-left:auto}.fluent-column-1{flex:0 0 auto;width:8.3333333333%;max-width:8.3333333333%}.fluent-column-offset-1{margin-left:8.3333333333%}.fluent-column-2{flex:0 0 auto;width:16.6666666667%;max-width:16.6666666667%}.fluent-column-offset-2{margin-left:16.6666666667%}.fluent-column-3{flex:0 0 auto;width:25%;max-width:25%}.fluent-column-offset-3{margin-left:25%}.fluent-column-4{flex:0 0 auto;width:33.3333333333%;max-width:33.3333333333%}.fluent-column-offset-4{margin-left:33.3333333333%}.fluent-column-5{flex:0 0 auto;width:41.6666666667%;max-width:41.6666666667%}.fluent-column-offset-5{margin-left:41.6666666667%}.fluent-column-6{flex:0 0 auto;width:50%;max-width:50%}.fluent-column-offset-6{margin-left:50%}.fluent-column-7{flex:0 0 auto;width:58.3333333333%;max-width:58.3333333333%}.fluent-column-offset-7{margin-left:58.3333333333%}.fluent-column-8{flex:0 0 auto;width:66.6666666667%;max-width:66.6666666667%}.fluent-column-offset-8{margin-left:66.6666666667%}.fluent-column-9{flex:0 0 auto;width:75%;max-width:75%}.fluent-column-offset-9{margin-left:75%}.fluent-column-10{flex:0 0 auto;width:83.3333333333%;max-width:83.3333333333%}.fluent-column-offset-10{margin-left:83.3333333333%}.fluent-column-11{flex:0 0 auto;width:91.6666666667%;max-width:91.6666666667%}.fluent-column-offset-11{margin-left:91.6666666667%}.fluent-column-12{flex:0 0 auto;width:100%;max-width:100%}.fluent-column-offset-12{margin-left:100%}@media (min-width: 576px){.fluent-column-sm-fill{flex:1 1 auto}.fluent-column-offset-sm-auto{margin-left:auto}.fluent-column-sm-1{flex:0 0 auto;width:8.3333333333%;max-width:8.3333333333%}.fluent-column-offset-sm-1{margin-left:8.3333333333%}.fluent-column-sm-2{flex:0 0 auto;width:16.6666666667%;max-width:16.6666666667%}.fluent-co