UNPKG

@dynamicforms/vue-forms

Version:

Data entry forms for vue - logic (no controls here)

1 lines 89.3 kB
{"version":3,"file":"dynamicforms-vue-forms.umd.cjs","sources":["../src/display-mode.ts","../src/actions/field-action-base.ts","../src/field.interface.ts","../src/actions/value-changed-action.ts","../src/validators/validation-error.ts","../src/validators/validator.ts","../src/actions/actions-map.ts","../src/actions/conditional/conditional-statement-action.ts","../src/actions/conditional/operator.ts","../src/actions/enabled-actions.ts","../src/actions/valid-changed-action.ts","../src/actions/visibility-actions.ts","../src/field-base.ts","../src/actions/conditional/statement.ts","../src/actions/execute-action.ts","../src/actions/list-item-added-action.ts","../src/actions/list-item-removed-action.ts","../src/field.ts","../src/action.ts","../src/group.ts","../src/list.ts","../src/config.ts","../src/validators/error-message-builder.ts","../src/validators/validator-compare-to.ts","../src/validators/validator-in-allowed-values.ts","../src/validators/validator-required.ts","../src/validators/validator-min-max-range.ts","../src/validators/validator-min-max-range-length.ts","../src/validators/validator-pattern.ts"],"sourcesContent":["/**\n * DisplayMode enum provides an enumeration for supported ways of rendering a particular object in the DOM\n */\nenum DisplayMode {\n // This enum is actually declared in dynamicforms.mixins.field_render.py\n SUPPRESS = 1, // Field will be entirely suppressed. it will not render (not even to JSON) and will not parse for PUT\n HIDDEN = 5, // Field will render as <input type=\"hidden\"> or <tr data-field_name>\n INVISIBLE = 8, // Field will render completely, but with display: none. Equal to setting its style = {display: none}\n FULL = 10, // Field will render completely\n}\n\nexport const defaultDisplayMode = DisplayMode.FULL;\n\nnamespace DisplayMode {\n export function fromString(mode: string): DisplayMode {\n if (mode.toUpperCase() === 'SUPPRESS') return DisplayMode.SUPPRESS;\n if (mode.toUpperCase() === 'HIDDEN') return DisplayMode.HIDDEN;\n if (mode.toUpperCase() === 'INVISIBLE') return DisplayMode.INVISIBLE;\n return defaultDisplayMode;\n }\n\n export function fromAny(mode: any): DisplayMode {\n const input = (typeof mode === 'number') ? mode : DisplayMode.fromString(mode as string);\n if (Object.values(DisplayMode).includes(input)) return input;\n return defaultDisplayMode;\n }\n\n export function isDefined(mode: number | string): boolean {\n const check = (typeof mode === 'number') ? mode : DisplayMode.fromString(mode as string);\n return Object.values(DisplayMode).includes(check);\n }\n}\n\nObject.freeze(DisplayMode);\n\nexport default DisplayMode;\n","// eslint-disable-next-line max-classes-per-file\nimport {\n FieldActionExecute,\n type IField,\n type IFieldAction,\n} from '@/field.interface';\n\ntype ActionExecutor = (field: IField, supr: FieldActionExecute, ...params: any[]) => any;\n\nexport default abstract class FieldActionBase implements IFieldAction {\n public static get classIdentifier(): symbol { throw new Error('classIdentifier must be declared'); }\n\n public get classIdentifier(): symbol { return (<any> this.constructor).classIdentifier; }\n\n private readonly executorFn: ActionExecutor;\n\n constructor(executorFn: ActionExecutor) {\n this.executorFn = executorFn;\n }\n\n execute(field: IField, supr: IFieldAction['execute'], ...params: any[]): any {\n return this.executorFn(field, supr, ...params);\n }\n\n // eslint-disable-next-line class-methods-use-this\n get eager() { return false; }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this\n boundToField(field: IField) { }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this\n unregister() {}\n}\n","import { ComputedRef } from 'vue';\n\nimport type DisplayMode from './display-mode';\nimport { type ValidationError } from './validators/validation-error';\n\nexport interface IField<T = any> {\n value: T;\n reactiveValue: ComputedRef<T>;\n fullValue: T;\n originalValue: T;\n valid: boolean;\n validating: boolean;\n errors: ValidationError[];\n enabled: boolean;\n visibility: DisplayMode;\n\n parent?: any; // Group when member of a Group, parent will specify that group\n fieldName?: string; // when member of a Group, fieldName specifies the name of this field\n\n clone(overrides?: Partial<IField<T>>): IField<T>;\n\n // events\n registerAction(action: IFieldAction<T>): this;\n triggerAction<T2 extends IFieldAction<T>>(actionClass: abstract new (...args: any[]) => T2, ...params: any[]): any;\n\n // API\n validate(): void;\n clearValidators(): void;\n isChanged: boolean;\n}\n\nexport interface IFieldConstructorActionsList<T = any> {\n actions?: IFieldAction<T>[],\n validators?: IFieldAction<T>[],\n}\n\nexport type IFieldConstructorParams<T = any> = IField<T> & IFieldConstructorActionsList<T>;\n\nexport class AbortEventHandlingException extends Error {}\n\nexport type FieldActionExecute<T = any> = (field: IField<T>, ...params: any[]) => any;\nexport interface IFieldAction<T = any> {\n execute(field: IField<T>, supr: FieldActionExecute<T>, ...params: any[]): any;\n}\n","import FieldActionBase from './field-action-base';\n\nimport { FieldActionExecute, type IField } from '@/field.interface';\n\nexport const ValueChangedActionClassIdentifier = Symbol('ValueChangedAction');\n\n// eslint-disable-next-line import/prefer-default-export\nexport class ValueChangedAction<T = any> extends FieldActionBase {\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(\n executorFn: (\n field: IField<T>, supr: FieldActionExecute<T>, newValue: T, oldValue: T,\n ) => void,\n ) {\n super(executorFn);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return ValueChangedActionClassIdentifier; }\n\n execute(\n field: IField<T>,\n supr: FieldActionExecute<T>,\n newValue: T,\n oldValue: T,\n ): void {\n return super.execute(field, supr, newValue, oldValue);\n }\n}\n","/* eslint-disable max-classes-per-file */\nimport { computed, ComputedRef, Ref, unref } from 'vue';\n\n/**\n * Marks content for markdown rendering\n */\nexport class MdString extends String {}\n\n/**\n * Interface for custom component content definition\n */\nexport interface CustomModalContentComponentDef {\n componentName: string;\n componentProps: Record<any, any>;\n}\n\n/**\n * Type for different renderable content formats: plain string, markdown, or custom component\n */\nexport type RenderContent = string | MdString | CustomModalContentComponentDef;\n/**\n * Type for different renderable content formats (supporting references): plain string, markdown, or custom component\n */\nexport type RenderContentRef = RenderContent | Ref<RenderContent>;\n\n/**\n * Type guard to check if content is a custom component definition\n * @param msg - Content to check\n * @returns True if content is a custom component definition\n */\nexport function isCustomModalContentComponentDef(msg?: RenderContentRef): msg is CustomModalContentComponentDef {\n const uMsg = unref(msg);\n return typeof uMsg === 'object' && 'componentName' in uMsg;\n}\n\n/**\n * Base validation error class with component rendering capabilities\n */\nexport class ValidationError {\n get componentName() { return 'Comment'; } // eslint-disable-line class-methods-use-this\n\n get componentBindings() { return {}; } // eslint-disable-line class-methods-use-this\n\n get componentBody() { return ''; } // eslint-disable-line class-methods-use-this\n}\n\n/**\n * Simple text-only ValidationError\n */\nexport class ValidationErrorText extends ValidationError {\n text: string;\n\n constructor(text: string) {\n super();\n this.text = text;\n }\n\n get componentName() { return 'template'; } // eslint-disable-line class-methods-use-this\n\n get componentBody() { return this.text; }\n}\n\n/**\n * Validation error that supports multiple content types (plain text, markdown, component)\n */\nexport class ValidationErrorRenderContent extends ValidationError {\n private text: RenderContent | Ref<RenderContent>;\n\n private textType: ComputedRef<'string' | 'md' | 'component'>;\n\n constructor(text: RenderContentRef) {\n super();\n this.text = text;\n this.textType = computed(() => this.getTextType);\n }\n\n get getTextType() {\n const msg = unref(this.text);\n if (!msg) return 'string';\n if (msg instanceof MdString) return 'md';\n if (isCustomModalContentComponentDef(msg)) return 'component';\n return 'string';\n }\n\n get componentName() {\n switch (unref(this.textType)) {\n case 'string': return 'template';\n case 'md': return 'vue-markdown';\n case 'component': return (unref(this.text) as CustomModalContentComponentDef).componentName;\n default: return 'template';\n }\n }\n\n get componentBindings() {\n switch (unref(this.textType)) {\n case 'string': return { };\n case 'md': return { source: this.text.toString() };\n case 'component': return (unref(this.text) as CustomModalContentComponentDef).componentProps;\n default: return { };\n }\n }\n\n get componentBody() {\n switch (unref(this.textType)) {\n case 'string': return unref(this.text) as string;\n default: return '';\n }\n }\n}\n\n/** ********************************************************************************************************************\n *\n at some point there will be classes here that will support links or action buttons or something even more complex\n *\n ******************************************************************************************************************** */\n","import { isEqual } from 'lodash-es';\n\nimport { ValueChangedAction } from '../actions/value-changed-action';\nimport { type FieldBase } from '../field-base';\nimport { FieldActionExecute, type IField } from '../field.interface';\n\nimport { isCustomModalContentComponentDef, MdString, RenderContentRef, ValidationError } from './validation-error';\n\nexport type ValidationFunctionResult = ValidationError[] | null;\nexport type ValidationFunction<T = any> = (newValue: T, oldValue: T, field: IField<T>) =>\n ValidationFunctionResult | Promise<ValidationFunctionResult>;\n\ninterface SourceProp { source: symbol }\n\nconst ValidatorClassIdentifier = Symbol('Validator');\n\n/**\n * Validator is a specialized action that performs validation when a field's value changes.\n * It automatically adds/removes validation errors from the field's errors array.\n */\nexport class Validator<T = any> extends ValueChangedAction {\n private readonly source: symbol;\n\n /**\n * Creates a new validator\n * @param validationFn Function that validates the field value and returns errors or null\n */\n constructor(validationFn: ValidationFunction<T>) {\n const executor = (field: IField<T>, supr: FieldActionExecute<T>, newValue: T, oldValue: T) => {\n const errors = validationFn(newValue, oldValue, field) || [];\n\n const processErrors = (err: ValidationFunctionResult) => {\n err?.forEach(\n (e) => Object.defineProperty(e, 'source', { value: this.source, enumerable: false, configurable: false }),\n );\n for (let i = field.errors.length - 1; i >= 0; i--) {\n const error = field.errors[i] as ValidationError & SourceProp;\n if (error.source === this.source) {\n const idx = err?.findIndex((e) => isEqual(e, error)) ?? -1;\n if (idx >= 0) err?.splice(idx, 1);\n else field.errors.splice(i, 1);\n }\n }\n\n if (err && err.length > 0) field.errors.push(...err);\n field.validate(); // Update the field's valid state\n };\n\n if (errors instanceof Promise) {\n // @ts-ignore\n field.validating = (++(<FieldBase> field).validatingCount) > 0;\n errors\n .then((err) => processErrors(err))\n .finally(() => {\n // @ts-ignore\n (<FieldBase> field).validatingCount = Math.max(0, (<FieldBase> field).validatingCount - 1);\n // @ts-ignore\n field.validating = (<FieldBase>field).validatingCount > 0;\n });\n } else processErrors(errors);\n return supr(field, newValue, oldValue); // Continue the action chain\n };\n\n super(executor);\n\n // Create a unique symbol for this validator instance\n this.source = Symbol(this.constructor.name);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return ValidatorClassIdentifier; }\n\n // eslint-disable-next-line class-methods-use-this\n get eager() { return true; }\n\n // eslint-disable-next-line class-methods-use-this\n protected replacePlaceholders(text: RenderContentRef, replace: Record<string, any>) {\n if (isCustomModalContentComponentDef(text)) return text;\n let ret = (text as string | MdString);\n Object.keys(replace).forEach((key) => { ret = ret.replaceAll(`{${key}}`, replace[key]); });\n return (text instanceof MdString) ? new MdString(ret) : ret;\n }\n}\n","import { type FieldActionExecute, type IField, AbortEventHandlingException } from '../field.interface';\nimport { Validator } from '../validators/validator';\n\nimport FieldActionBase from './field-action-base';\nimport { ValueChangedActionClassIdentifier } from './value-changed-action';\n\nexport default class ActionsMap extends Map<symbol, FieldActionExecute> {\n private readonly eagerActions = new Set<symbol>();\n\n private readonly registeredActions: FieldActionBase[] = [];\n\n register(action: FieldActionBase) {\n if (!(action instanceof FieldActionBase)) throw new Error('Invalid action type');\n this.registeredActions.push(action);\n\n const actionType = action.classIdentifier;\n const existingExecute = this.get(actionType) || (() => null);\n\n function ex(field: IField, ...params: any[]) {\n return action.execute(field, existingExecute, ...params);\n }\n this.set(actionType, ex);\n if (action.eager) this.eagerActions.add(action.classIdentifier);\n }\n\n trigger<T extends FieldActionBase>(\n ActionClass: { new (...args: any[]): T, classIdentifier: symbol },\n field: IField,\n ...params: any[]\n ): any {\n const identifier = ActionClass.classIdentifier;\n if (identifier === ValueChangedActionClassIdentifier) this.triggerEager(field, ...params);\n const execute = this.get(identifier);\n try {\n if (execute) return execute(field, ...params);\n } catch (error) {\n if (!(error instanceof AbortEventHandlingException)) throw error;\n }\n return null;\n }\n\n triggerEager(field: IField, ...params: any[]): any {\n for (const identifier of this.eagerActions) {\n const execute = this.get(identifier);\n try {\n if (execute) execute(field, ...params);\n } catch (error) {\n if (!(error instanceof AbortEventHandlingException)) throw error;\n }\n }\n }\n\n clone() : ActionsMap {\n const newActions = new ActionsMap();\n this.registeredActions.forEach((action) => newActions.register(action));\n return newActions;\n }\n\n cloneWithoutValidators(): ActionsMap {\n const newActions = new ActionsMap();\n this.registeredActions.forEach((action) => {\n if (action instanceof Validator) action.unregister();\n else newActions.register(action);\n });\n return newActions;\n }\n}\n","// eslint-disable-next-line max-classes-per-file\nimport DisplayMode from '../../display-mode';\nimport { FieldActionExecute, IField } from '../../field.interface';\nimport { ValueChangedAction } from '../value-changed-action';\n\nimport { Statement } from './statement';\n\nconst ConditionalStatementActionClassIdentifier = Symbol('ConditionalStatementAction');\n\ntype ConditionalExecutorFn = (\n field: IField,\n currentResult: boolean,\n previousResult: boolean | undefined,\n) => void;\n\nexport class ConditionalStatementAction extends ValueChangedAction {\n private lastResult: boolean | undefined = undefined;\n\n // boundField tracks the fields this action is bound to. so that it may perform the executorFn on them and not on the\n // ValueChangedAction fields that will call the actionExecutor\n private readonly boundFields: Set<IField> = new Set<IField>();\n\n constructor(statement: Statement, executorFn: ConditionalExecutorFn) {\n // Create ValueChangedAction executor that will evaluate statement and track changes\n const actionExecutor = (field: IField, supr: FieldActionExecute, newValue: boolean, oldValue: boolean) => {\n const currentResult = statement.evaluate();\n\n if (currentResult !== this.lastResult) {\n for (const fld of this.boundFields) {\n executorFn(fld, currentResult, this.lastResult);\n }\n this.lastResult = currentResult;\n }\n\n // Continue action chain\n return supr(field, newValue, oldValue);\n };\n\n super(actionExecutor);\n statement.collectFields().forEach((field) => field.registerAction(new ValueChangedAction(actionExecutor)));\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return ConditionalStatementActionClassIdentifier; }\n\n // eslint-disable-next-line class-methods-use-this\n get eager() { return true; }\n\n boundToField(field: IField) { this.boundFields.add(field); }\n}\n\n// Derived classes for visibility, enabled, and value changes\n\nexport class ConditionalVisibilityAction extends ConditionalStatementAction {\n constructor(statement: Statement) {\n super(statement, (field: IField, currentResult) => {\n field.visibility = currentResult ? DisplayMode.FULL : DisplayMode.SUPPRESS;\n });\n }\n}\n\nexport class ConditionalEnabledAction extends ConditionalStatementAction {\n constructor(statement: Statement) {\n super(statement, (field, currentResult) => {\n field.enabled = currentResult;\n });\n }\n}\n\nexport class ConditionalValueAction<T> extends ConditionalStatementAction {\n constructor(statement: Statement, trueValue: T) {\n super(statement, (field, currentResult) => {\n if (currentResult) field.value = trueValue;\n });\n }\n}\n","/**\n * Operators provides us a functionality for backend to send us complex condition upon which we send\n * dynamic visibility prop for form input fields\n */\nenum Operator {\n // Logic Operators\n NOT = 0,\n OR = 1,\n AND = 2,\n XOR = 3,\n NAND = 4,\n NOR = 5,\n\n // Comparators (comparison operators)\n EQUALS = -1,\n NOT_EQUALS = -2,\n GT = -3,\n LT = -4,\n GE = -5,\n LE = -6,\n IN = -7,\n NOT_IN = -8,\n INCLUDES = -9,\n NOT_INCLUDES = -10,\n}\n\nnamespace Operator {\n export function fromString(operator: string): Operator {\n const op = operator.toLowerCase();\n if (op === 'not') return Operator.NOT;\n if (op === 'or') return Operator.OR;\n if (op === 'and') return Operator.AND;\n if (op === 'xor') return Operator.XOR;\n if (op === 'nand') return Operator.NAND;\n if (op === 'nor') return Operator.NOR;\n\n if (op === 'equals') return Operator.EQUALS;\n if (['not_equals', 'not-equals', 'not equals'].includes(op)) return Operator.NOT_EQUALS;\n if (op === 'gt') return Operator.GT;\n if (op === 'lt') return Operator.LT;\n if (op === 'ge') return Operator.GE;\n if (op === 'le') return Operator.LE;\n if (op === 'in') return Operator.IN;\n if (['not_in', 'not-in', 'not in'].includes(op)) return Operator.NOT_IN;\n if (op === 'includes') return Operator.INCLUDES;\n if (['not_includes', 'not-includes', 'not includes'].includes(op)) return Operator.NOT_INCLUDES;\n throw new Error(`Unrecognised operator ${op}`);\n }\n\n export function fromAny(mode: any): Operator {\n const input = (typeof mode === 'number') ? mode : Operator.fromString(mode as string);\n if (Object.values(Operator).includes(input)) return input;\n throw new Error(`Unrecognised operator ${mode}`);\n }\n\n export function isDefined(operator: number | string): boolean {\n const check = (typeof operator === 'number') ? operator : Operator.fromString(operator as string);\n return Object.values(Operator).includes(check);\n }\n\n // c8 bug: it doesn't matter what there is in the next line (e.g. console.log()).\n // it will always be a branch with one branch not covered\n export function isLogicOperator(operator: Operator): boolean {\n return operator >= 0;\n }\n}\n\nObject.freeze(Operator);\n\nexport default Operator;\n","// eslint-disable-next-line max-classes-per-file\nimport FieldActionBase from './field-action-base';\n\nimport { FieldActionExecute, type IField } from '@/field.interface';\n\nconst EnabledChangingActionClassIdentifier = Symbol('EnabledChangingAction');\n\nexport class EnabledChangingAction extends FieldActionBase {\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(\n executorFn: (\n field: IField, supr: FieldActionExecute, newValue: boolean, oldValue: boolean\n ) => boolean,\n ) {\n super(executorFn);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return EnabledChangingActionClassIdentifier; }\n\n execute(field: IField, supr: FieldActionExecute, newValue: boolean, oldValue: boolean): boolean {\n return super.execute(field, supr, newValue, oldValue);\n }\n}\n\nconst EnabledChangedActionClassIdentifier = Symbol('EnabledChangedAction');\n\nexport class EnabledChangedAction extends FieldActionBase {\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(\n executorFn: (\n field: IField, supr: FieldActionExecute, newValue: boolean, oldValue: boolean\n ) => void,\n ) {\n super(executorFn);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return EnabledChangedActionClassIdentifier; }\n\n execute(field: IField, supr: FieldActionExecute, newValue: boolean, oldValue: boolean): void {\n return super.execute(field, supr, newValue, oldValue);\n }\n}\n","import FieldActionBase from './field-action-base';\n\nimport { FieldActionExecute, type IField } from '@/field.interface';\n\nconst ValidChangedActionClassIdentifier = Symbol('ValidChangedAction');\n\n// eslint-disable-next-line import/prefer-default-export\nexport class ValidChangedAction extends FieldActionBase {\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(\n executorFn: (\n field: IField, supr: FieldActionExecute, newValue: boolean, oldValue: boolean,\n ) => void,\n ) {\n super(executorFn);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return ValidChangedActionClassIdentifier; }\n\n execute(\n field: IField,\n supr: FieldActionExecute,\n newValue: boolean,\n oldValue: boolean,\n ): void {\n return super.execute(field, supr, newValue, oldValue);\n }\n}\n","// eslint-disable-next-line max-classes-per-file\nimport FieldActionBase from './field-action-base';\n\nimport DisplayMode from '@/display-mode';\nimport { FieldActionExecute, type IField } from '@/field.interface';\n\nconst VisibilityChangingActionClassIdentifier = Symbol('VisibilityChangingAction');\n\nexport class VisibilityChangingAction extends FieldActionBase {\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(\n executorFn: (\n field: IField, supr: FieldActionExecute, newValue: DisplayMode, oldValue: DisplayMode\n ) => DisplayMode,\n ) {\n super(executorFn);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return VisibilityChangingActionClassIdentifier; }\n\n execute(\n field: IField,\n supr: FieldActionExecute,\n newValue: DisplayMode,\n oldValue: DisplayMode,\n ): DisplayMode {\n return super.execute(field, supr, newValue, oldValue);\n }\n}\n\nconst VisibilityChangedActionClassIdentifier = Symbol('VisibilityChangedAction');\n\nexport class VisibilityChangedAction extends FieldActionBase {\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(\n executorFn: (\n field: IField, supr: FieldActionExecute, newValue: DisplayMode, oldValue: DisplayMode,\n ) => void,\n ) {\n super(executorFn);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return VisibilityChangedActionClassIdentifier; }\n\n execute(\n field: IField,\n supr: FieldActionExecute,\n newValue: DisplayMode,\n oldValue: DisplayMode,\n ): void {\n return super.execute(field, supr, newValue, oldValue);\n }\n}\n","import { isBoolean, isEqual } from 'lodash-es';\nimport { computed } from 'vue';\n\nimport ActionsMap from './actions/actions-map';\nimport { EnabledChangedAction, EnabledChangingAction } from './actions/enabled-actions';\nimport FieldActionBase from './actions/field-action-base';\nimport { ValidChangedAction } from './actions/valid-changed-action';\nimport { VisibilityChangedAction, VisibilityChangingAction } from './actions/visibility-actions';\nimport DisplayMode from './display-mode';\nimport { IField, IFieldAction } from './field.interface';\nimport { type Group } from './group';\nimport { ValidationError } from './validators/validation-error';\n\n// eslint-disable-next-line import/prefer-default-export\nexport abstract class FieldBase<T = any> implements IField<T> {\n abstract get value(): T;\n abstract set value(newValue: T);\n\n public readonly reactiveValue = computed(() => this.value);\n\n abstract clone(overrides?: Partial<IField<T>>): IField<T>;\n\n declare originalValue: T; // contains original field value as was provided at creation\n\n protected validatingCount = 0;\n\n public readonly validating = false;\n\n protected _valid: boolean = true; // is current value valid as per FE and BE validators?\n\n errors: ValidationError[] = []; // list of errors\n\n declare parent?: Group; // when member of a Group, parent will specify that group\n\n declare fieldName?: string; // when member of a Group, fieldName specifies the name of this field\n\n protected actions: ActionsMap = new ActionsMap();\n\n // default property handlers\n private _visibility: DisplayMode = DisplayMode.FULL;\n\n get visibility(): DisplayMode { return this._visibility; }\n\n set visibility(newValue: DisplayMode) {\n const oldValue = this._visibility;\n const alteredValue = this.actions.trigger(VisibilityChangingAction, this, newValue, oldValue);\n if (!DisplayMode.isDefined(alteredValue ?? newValue)) throw new Error('visibility must be a DisplayMode constant');\n this._visibility = DisplayMode.fromAny(alteredValue ?? newValue);\n this.actions.trigger(VisibilityChangedAction, this, this._visibility, oldValue);\n }\n\n private _enabled: boolean = true;\n\n get enabled(): boolean { return this._enabled; }\n\n set enabled(newValue: boolean) {\n const oldValue = this._enabled;\n const alteredValue = this.actions.trigger(EnabledChangingAction, this, newValue, oldValue);\n if (!isBoolean(alteredValue ?? newValue)) throw new Error('Enabled value must be boolean');\n this._enabled = alteredValue ?? newValue;\n this.actions.trigger(EnabledChangedAction, this, this._enabled, oldValue);\n }\n\n validate() {\n const oldValid = this._valid;\n this._valid = this.valid;\n if (this._valid !== oldValid) this.actions.trigger(ValidChangedAction, this, this.valid, oldValid);\n }\n\n get valid() {\n return this.errors.length === 0;\n }\n\n get fullValue(): any {\n return this.value;\n }\n\n get isChanged() : boolean {\n return !isEqual(this.value, this.originalValue);\n }\n\n registerAction(action: IFieldAction<T>): this {\n const act = action as FieldActionBase;\n this.actions.register(act);\n act.boundToField(this);\n if (act.eager) {\n // When adding eager actions, execute them immediately\n this.actions.trigger(Object.getPrototypeOf(action).constructor, this, this.value, this.originalValue);\n }\n return this;\n }\n\n triggerAction<T2 extends IFieldAction<T>>(\n actionClass: new (...args: any[]) => T2,\n ...params: any[]\n ): any {\n return this.actions.trigger(actionClass as any, this, ...params);\n }\n\n clearValidators(): void {\n this.actions = this.actions.cloneWithoutValidators();\n this.errors = [];\n this._valid = true;\n }\n}\n","import { isString } from 'lodash-es';\nimport { unref } from 'vue';\n\nimport { FieldBase } from '../../field-base';\nimport { IField } from '../../field.interface';\n\nimport Operator from './operator';\n\nexport type OperandType = any | Statement | IField;\n\nfunction XOR(value1: boolean, value2: boolean): boolean {\n return value1 ? !value2 : value2;\n}\n\nexport class Statement {\n private readonly operand1: OperandType;\n\n private readonly operator: Operator;\n\n private readonly operand2: OperandType;\n\n constructor(operand1: OperandType, operator: Operator, operand2: OperandType) {\n this.operand1 = operand1;\n this.operator = operator;\n this.operand2 = operand2;\n }\n\n get operand1Value() {\n if (this.operand1 instanceof Statement) return this.operand1.evaluate();\n if (this.operand1 instanceof FieldBase) return unref(this.operand1.value);\n return this.operand1; // any\n }\n\n get operand2Value() {\n if (this.operand2 instanceof Statement) return this.operand2.evaluate();\n if (this.operand2 instanceof FieldBase) return unref(this.operand2.value);\n return this.operand2; // any\n }\n\n evaluate(): boolean {\n const operand1 = this.operand1Value;\n const operand2 = this.operand2Value;\n\n switch (this.operator) {\n // logical operators\n case Operator.AND:\n return operand1 && operand2;\n case Operator.OR:\n return operand1 || operand2;\n case Operator.NAND:\n return !(operand1 && operand2);\n case Operator.NOR:\n return !(operand1 || operand2);\n case Operator.XOR:\n return XOR(operand1, operand2);\n case Operator.NOT:\n return !operand1;\n\n // comparison operators\n case Operator.EQUALS:\n return operand1 == operand2; // eslint-disable-line eqeqeq\n case Operator.NOT_EQUALS:\n return operand1 != operand2; // eslint-disable-line eqeqeq\n case Operator.LT:\n return operand1 < operand2;\n case Operator.LE:\n return operand1 <= operand2;\n case Operator.GE:\n return operand1 >= operand2;\n case Operator.GT:\n return operand1 > operand2;\n case Operator.IN:\n return operand2?.includes?.(operand1) ?? false;\n case Operator.NOT_IN:\n return !(operand2?.includes?.(operand1) ?? true);\n case Operator.INCLUDES:\n return isString(operand1) && isString(operand2) && operand1.indexOf(operand2) >= 0;\n case Operator.NOT_INCLUDES:\n return !(isString(operand1) && isString(operand2) && operand1.indexOf(operand2) >= 0);\n\n default:\n throw new Error(`Operator not implemented ${this.operator}`);\n }\n }\n\n /**\n * Recursively collects all fields used in this statement and its nested statements\n * @returns A set of all fields used in this statement\n */\n collectFields(): Set<IField> {\n const fields = new Set<IField>();\n\n function processOperand(op: OperandType) {\n if (op instanceof FieldBase) {\n fields.add(op);\n } else if (op instanceof Statement) {\n // For nested statements, merge their fields with our collection\n const nestedFields = op.collectFields();\n nestedFields.forEach((field) => fields.add(field));\n }\n }\n processOperand(this.operand1);\n processOperand(this.operand2);\n\n return fields;\n }\n}\n","import FieldActionBase from './field-action-base';\n\nimport { FieldActionExecute, type IField } from '@/field.interface';\n\nconst ExecuteActionClassIdentifier = Symbol('ExecuteAction');\n\n// eslint-disable-next-line import/prefer-default-export\nexport class ExecuteAction extends FieldActionBase {\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(\n executorFn: (field: IField, supr: FieldActionExecute, params: any) => any,\n ) {\n super(executorFn);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return ExecuteActionClassIdentifier; }\n\n execute(field: IField, supr: FieldActionExecute, params: any): any {\n return super.execute(field, supr, params);\n }\n}\n","import FieldActionBase from './field-action-base';\n\nimport { FieldActionExecute, type IField } from '@/field.interface';\n\nconst ListItemAddedActionClassIdentifier = Symbol('ListItemAddedAction');\n\n// eslint-disable-next-line import/prefer-default-export\nexport class ListItemAddedAction extends FieldActionBase {\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(\n executorFn: (field: IField, supr: FieldActionExecute, item: any, index: number) => void,\n ) {\n super(executorFn);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return ListItemAddedActionClassIdentifier; }\n\n execute(field: IField, supr: FieldActionExecute, item: any, index: number): void {\n return super.execute(field, supr, item, index);\n }\n}\n","import FieldActionBase from './field-action-base';\n\nimport { FieldActionExecute, type IField } from '@/field.interface';\n\nconst ListItemRemovedActionClassIdentifier = Symbol('ListItemRemovedAction');\n\n// eslint-disable-next-line import/prefer-default-export\nexport class ListItemRemovedAction extends FieldActionBase {\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(\n executorFn: (field: IField, supr: FieldActionExecute, item: any, index: number) => void,\n ) {\n super(executorFn);\n }\n\n // eslint-disable-next-line class-methods-use-this\n static get classIdentifier() { return ListItemRemovedActionClassIdentifier; }\n\n execute(field: IField, supr: FieldActionExecute, item: any, index: number): void {\n return super.execute(field, supr, item, index);\n }\n}\n","import { reactive } from 'vue';\n\nimport { ValueChangedAction } from './actions';\nimport { FieldBase } from './field-base';\nimport { IField, IFieldConstructorParams } from './field.interface';\n\nconst fieldConstructorGuard = Symbol('FieldConstructorGuard');\n\nclass Field<T = any> extends FieldBase {\n protected _value: T = undefined!;\n\n constructor(guard?: symbol) {\n super();\n if (guard !== fieldConstructorGuard) {\n const cn = this.constructor.name;\n throw new TypeError(`Don't use constructor to instantiate ${cn}. Use ${cn}.create<T>`);\n }\n }\n\n protected init(params?: Partial<IFieldConstructorParams<T>>) {\n if (params) {\n const { value: paramValue, validators, actions, ...otherParams } = params;\n [...(validators || []), ...(actions || [])].forEach((a) => this.registerAction(a));\n Object.assign(this, otherParams);\n this._value = paramValue ?? this.originalValue;\n if (this.originalValue === undefined) this.originalValue = this._value;\n }\n this.actions.triggerEager(this, this.value, this.originalValue);\n this.validate();\n }\n\n /**\n * Creates a new reactive Field instance.\n * @param params Initial field parameters\n * @returns Reactive Field instance\n */\n static create<T = any>(\n this: new(guard?: symbol) => Field<T>,\n params?: Partial<IFieldConstructorParams<T>>,\n ): InstanceType<typeof this> {\n const res = reactive(new this(fieldConstructorGuard)) as any;\n res.init(params);\n return res;\n }\n\n get value() { return this._value; }\n\n set value(newValue: T) {\n const oldValue = this._value;\n if (!this.enabled || oldValue === newValue) return; // a disabled field does not allow changing value\n this._value = newValue;\n this.actions.trigger(ValueChangedAction, this, this._value, oldValue);\n if (this.parent) this.parent.notifyValueChanged();\n this.validate();\n }\n\n clone(overrides?: Partial<IField<T>>): this {\n const res: this = (this.constructor as any).create({\n value: overrides?.value ?? this.value,\n ...(overrides && 'originalValue' in overrides ? { originalValue: overrides.originalValue } : { }),\n enabled: overrides?.enabled ?? this.enabled,\n visibility: overrides?.visibility ?? this.visibility,\n });\n res.actions = this.actions.clone();\n res.actions.triggerEager(res, res.value, res.originalValue);\n return res;\n }\n}\n\nexport { Field };\n\nexport type NullableField<T = any> = Field<T> | null;\n\nexport const EmptyField = Field.create({ value: 'EmptyField' })\n .registerAction(new ValueChangedAction(\n () => {\n console.warn('Working with EmptyField! This should not happen');\n },\n ));\n","import { ExecuteAction } from './actions';\nimport { Field } from './field';\nimport { IFieldConstructorParams } from './field.interface';\n\nexport interface ActionValue {\n label?: string;\n icon?: string;\n}\n\nfunction isValEmpty(val: ActionValue | undefined, defaultIfTrue: ActionValue): ActionValue {\n if (val?.label == null && val?.icon == null) return defaultIfTrue;\n return val;\n}\n\n// @ts-ignore: prevent TS from complaining how create method is not ok because its declaration differs from Field's\nexport class Action<T extends ActionValue = ActionValue> extends Field<T> {\n constructor(guard?: symbol) {\n super(guard);\n this._value = { label: undefined, icon: undefined } as T;\n }\n\n protected init(params?: Partial<IFieldConstructorParams<T>>) {\n // TODO: this init is most likely not needed any more. The only read diff from Field.init is the orgVal handling\n if (params) {\n const { value: paramValue, originalValue, validators, actions, ...otherParams } = params;\n [...(validators || []), ...(actions || [])].forEach((a) => this.registerAction(a));\n Object.assign(this, otherParams);\n const val = isValEmpty(paramValue, this._value);\n const orgVal = Object.freeze({ label: originalValue?.label, icon: originalValue?.icon } as ActionValue);\n this._value = isValEmpty(val, orgVal) as T;\n this.originalValue = isValEmpty(orgVal, val) as T;\n }\n this.actions.triggerEager(this, this.value, this.originalValue);\n this.validate();\n }\n\n static create<T extends ActionValue = ActionValue>(\n params?: Partial<IFieldConstructorParams<T>>,\n ): Action<T> {\n return super.create<T>(params) as Action<T>;\n }\n\n get icon(): string | undefined {\n return this.value.icon;\n }\n\n set icon(newValue: string | undefined) {\n this.value.icon = newValue;\n }\n\n get label(): string | undefined {\n return this.value.label;\n }\n\n set label(newValue: string | undefined) {\n this.value.label = newValue;\n }\n\n execute(params: any) {\n this.actions.trigger(ExecuteAction, this, params);\n }\n}\n\nexport type NullableAction = Action | null;\n","import { isEmpty, isEqual } from 'lodash-es';\nimport { computed } from 'vue';\n\nimport { ValueChangedAction } from './actions';\nimport { Field } from './field';\nimport { FieldBase } from './field-base';\nimport { IField, IFieldConstructorParams } from './field.interface';\n\nexport type GenericFieldsInterface = Record<string, IField>;\n// Utility tip za pretvorbo field strukture v value strukturo\ntype FieldsToValues<T extends GenericFieldsInterface> = {\n [K in keyof T]: T[K] extends IField<infer U> ? U : T[K] extends Group<infer G> ? FieldsToValues<G> : any;\n};\n\nexport class Group<T extends GenericFieldsInterface = GenericFieldsInterface> extends FieldBase {\n private readonly _fields: T;\n\n private _value: FieldsToValues<T> | null = null;\n\n public override readonly reactiveValue = computed<FieldsToValues<T> | null>(() => this.value);\n\n private suppressNotifyValueChanged: boolean = false;\n\n constructor(fields: T, params?: Partial<IFieldConstructorParams>) {\n super();\n\n if (!Group.isValidFields(fields)) throw new Error('Invalid fields object provided');\n this._fields = {} as T;\n Object.entries(fields).forEach(([name, field]) => this.addField(name, field));\n\n if (params) {\n const { value: paramValue, validators, actions, ...otherParams } = params;\n [...(validators || []), ...(actions || [])].forEach((a) => this.registerAction(a));\n Object.assign(this, otherParams);\n this.value = paramValue ?? this.originalValue;\n }\n\n if (this.originalValue === undefined) this.originalValue = this.value;\n\n // if (Object.keys(this._fields).length) console.log('group created', this, Error().stack);\n this.actions.triggerEager(this, this.value, this.originalValue);\n this.validate();\n }\n\n private addField(fieldName: string, field: IField) {\n // note: not sure if I should expose this (make it public).\n // breaks types, neglects events (originalValue, valueChanged), etc.\n if (this.fields[fieldName] !== undefined) {\n throw new Error(`Field ${fieldName} is already in this form`);\n }\n Object.defineProperty(field, 'parent', { get: () => this, configurable: false, enumerable: false });\n Object.defineProperty(field, 'fieldName', { get: () => fieldName, configurable: false, enumerable: false });\n Object.defineProperty(\n this._fields,\n fieldName,\n { get() { return field; }, configurable: false, enumerable: true },\n );\n }\n\n private static isValidFields(flds: unknown): flds is Record<string, FieldBase> {\n function isFieldAll(field: unknown): field is FieldBase {\n return field instanceof FieldBase;\n }\n\n return typeof flds === 'object' &&\n flds !== null &&\n Object.entries(flds).every(([, field]) => isFieldAll(field));\n }\n\n static createFromFormData(data: Record<string, any> | null): Group {\n if (data instanceof FieldBase) {\n throw new Error('data is already a Form structure, should be a simple object');\n }\n return new Group(\n data == null ?\n { } :\n Object.fromEntries(\n Object.entries(data).map(([key, value]) => [key, Field.create({ value })]),\n ),\n );\n }\n\n field<K extends keyof T>(fieldName: K): T[K] | null {\n return this._fields[fieldName] ?? null;\n }\n\n get fields(): T {\n return this._fields;\n }\n\n get value(): FieldsToValues<T> | null {\n const val = {} as Record<string, any>;\n Object.entries(this._fields).forEach(([name, field]) => {\n const fieldValue = field.value;\n if (field.enabled) {\n // readOnly fields do not serialize\n val[name] = fieldValue;\n } else if (field instanceof Group && !isEmpty(fieldValue)) {\n // readOnly group only serializes if it is non-empty (some of its fields are not readOnly)\n val[name] = fieldValue;\n }\n });\n return isEmpty(val) ? null : val as FieldsToValues<T>;\n }\n\n set value(newValue: FieldsToValues<T> | null) {\n this.suppressNotifyValueChanged = true;\n try {\n Object.entries(this._fields).forEach(([name, field]) => {\n if (newValue == null || name in newValue) {\n field.value = newValue == null ? null : newValue[name];\n }\n });\n } finally {\n this.suppressNotifyValueChanged = false;\n }\n this.notifyValueChanged();\n this.validate();\n }\n\n get fullValue(): Record<string, any> {\n const value: Record<string, any> = {};\n Object.entries(this._fields).forEach(([name, field]) => { value[name] = field.fullValue; });\n return value;\n }\n\n notifyValueChanged() {\n if (this.suppressNotifyValueChanged) return;\n const newValue = this.value;\n if (!isEqual(newValue, this._value)) {\n const oldValue = this._value;\n this._value = newValue;\n this.actions.trigger(ValueChangedAction, this, newValue, oldValue);\n if (this.parent) this.parent.notifyValueChanged();\n this.validate();\n }\n }\n\n get valid() {\n return super.valid && Object.values(this._fields).every((field) => field.valid);\n }\n\n clone(overrides?: Partial<IField>): Group<T> {\n const newFields = {} as T;\n Object.entries(this._fields).forEach(([name, field]) => {\n newFields[name as keyof T] = field.clone() as any;\n });\n const res = new Group(newFields, {\n value: overrides?.value ?? this.value,\n ...(overrides && 'originalValue' in overrides ? { originalValue: overrides.originalValue } : { }),\n enabled: overrides?.enabled ?? this.enabled,\n visibility: overrides?.visibility ?? this.visibility,\n });\n res.actions = this.actions.clone();\n res.actions.triggerEager(res, res.value, res.originalValue);\n return res;\n }\n}\n\nexport type NullableGroup = Group | null;\n","import { isEmpty, isEqual } from 'lodash-es';\n\nimport { ListItemAddedAction, ListItemRemovedAction, ValueChangedAction } from './actions';\nimport { FieldBase } from './field-base';\nimport { IField, IFieldConstructorParams } from './field.interface';\nimport { GenericFieldsInterface, Group } from './group';\n\nexport class List<T extends GenericFieldsInterface = GenericFieldsInterface> extends FieldBase {\n private _value: Group<T>[] | null = null;\n\n private _itemTemplate?: Group<T>;\n\n private _previousValue: Record<string, any>[] | null;\n\n constructor(itemTemplate?: Group<T>, params?: Partial<IFieldConstructorParams>) {\n super();\n\n this._itemTemplate = itemTemplate;\n\n if (params) {\n const { value: paramValue, validators, actions, ...otherParams } = params;\n [...(validators || []), ...(actions || [])].forEach((a) => this.registerAction(a));\n Object.assign(this, otherParams);\n\n if (paramValue) this.setValueInternal(paramValue);\n }\n\n if (this.originalValue === undefined) this.originalValue = this.value;\n this._previousValue = this.value;\n // if (Object.keys(this._fields).length) console.log('formGroup created', this, Error().stack);\n this.actions.triggerEager(this, this.value, this.originalValue);\n this.validate();\n }\n\n private processSetValueItem(item: any) : Group<T> {\n let res: Group<T>;\n // If item is already a Group, use it\n if (item instanceof Group) res = item;\n // Otherwise create a Group from item\n // eslint-disable-next-line no-underscore-dangle\n else if (this._itemTemplate) res = this._itemTemplate.clone({ value: item });\n else res = Group.createFromFormData(item) as Group<T>;\n\n Object.defineProperty(res, 'parent', { get: () => this, configurable: false, enumerable: false });\n\n return res;\n }\n\n private setValueInternal(newValue: any[]) {\n if (Array.isArray(newValue)) {\n this._value = newValue.map((item: any) => this.processSetValueItem(item));\n }\n }\n\n get value(): Record<string, any>[] | null {\n const value = this._value?.map((item) => item.value);\n return isEmpty(value) ? null : <Record<string, any>[]> value;\n }\n\n set value(newValue: Record<string, any>[]) {\n const oldValue = this.value;\n this.setValueInternal(newValue);\n this.actions.trigger(ValueChangedAction, this, this.value, oldValue);\n if (this.parent) this.parent.notifyValueChanged();\n this.validate();\n }\n\n clone(overrides?: Partial<IField>): List<T> {\n const res = new List(this._itemTemplate?.clone(), {\n value: [...(overrides?.value ?? this.value)],\n ...(overrides && 'originalValue' in overrides ? { originalValue: overrides.originalValue } : { }),\n enabled: overrides?.enabled ?? this.enabled,\n visibility: overrides?.visibility ?? this.visibility,\n });\n res.actions = this.actions.clone();\n res.actions.triggerEager(res, res.value, res.originalValue);\n return res;\n }\n\n notifyValueChanged() {\n const newValue = this.value;\n if (!isEqual(newValue, this._previousValue)) {\n const oldValue = this._previousValue;\n this._previousValue = newValue;\n this.actions.trigger(ValueChangedAction, this, newValue, oldValue);\n if (this.parent) this.parent.notifyValueChanged();\n this.validate();\n }\n }\n\n get valid() {\n return super.valid && (this._value?.every((item) => item.valid) ?? true);\n }\n\n get(index: number): Group<T> | undefined {\n return this._value != null ? this._value[index] : undefined;\n }\n\n push(item: any): number {\n return this.insert(item, this._value?.length ?? 0) + 1;\n }\n\n pop(): Group<T> | undefined {\n return this.remove((this._value?.length ?? 0) - 1);\n }\n\n remove(index: number): Group<T> | undefined {\n if (this._value == null || index < 0 || this._value.length <= index) return undefined;\n\n let removedItem = this._value.splice(index, 1)?.[0];\n\n if (removedItem) {\n // Remove parent reference\n removedItem = removedItem.clone();\n\n // Trigger events\n this.actions.trigger(ListItemRemovedAction, this, removedItem, index);\n this.notifyValueChanged();\n }\n\n return removedItem;\n }\n\n insert(item: any, index: number): number {\n if (this._value == null) this._value = [];\n while (this._value.length < index) {\n // if the index is too large for current array size, we add as many as necessary\n const itm = this.processSetValueItem(null);\n const idx = this._value.push(itm);\n this.actions.trigger(ListItemAddedAction, this, itm, idx);\n }\n const itm = this.processSetValueItem(item);\n this._value.splice(index, 0, itm);\n\n this.actions.trigger(ListItemAddedAction, this, itm, index);\n this.notifyValueChanged();