@dynamicforms/vue-forms
Version:
Data entry forms for vue - logic (no controls here)
1 lines • 96.8 kB
Source Map (JSON)
{"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/components/messages-widget.vue","../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\n// eslint-disable-next-line @typescript-eslint/no-namespace, no-redeclare\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","import { FieldActionExecute, type IField, type IFieldAction } 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 {\n throw new Error('classIdentifier must be declared');\n }\n\n public get classIdentifier(): symbol {\n return (<any>this.constructor).classIdentifier;\n }\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 get eager() {\n return false;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n boundToField(field: IField) {}\n\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 touched: boolean;\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(revalidate?: boolean): 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\nexport class ValueChangedAction<T = any> extends FieldActionBase {\n constructor(executorFn: (field: IField<T>, supr: FieldActionExecute<T>, newValue: T, oldValue: T) => void) {\n super(executorFn);\n }\n\n static get classIdentifier() {\n return ValueChangedActionClassIdentifier;\n }\n\n execute(field: IField<T>, supr: FieldActionExecute<T>, newValue: T, oldValue: T): void {\n return super.execute(field, supr, newValue, oldValue);\n }\n}\n","import { computed, ComputedRef, Ref, unref } from 'vue';\n\n/**\n * Marks content for markdown rendering\n */\nexport class MdString extends String {\n plugins?: any[];\n options?: any;\n\n constructor(value: string, options?: any, plugins?: any[]) {\n super(value);\n this.plugins = plugins;\n this.options = options;\n }\n}\n\n/**\n * Interface for custom component content definition\n */\nexport interface SimpleComponentDef {\n componentName: string;\n componentProps?: Record<any, any>;\n componentVHtml?: string;\n}\n\nexport type ClassType = string | string[] | Record<string, boolean>;\nexport type ClassTypes = ClassType | ClassType[];\n\nexport type RenderContentNonCallable = string | MdString | SimpleComponentDef;\nexport type RenderContentCallable = () => RenderContentNonCallable;\n/**\n * Type for different renderable content formats: plain string, markdown, or custom component\n */\nexport type RenderContent = RenderContentNonCallable | RenderContentCallable;\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 isSimpleComponentDef(msg?: RenderContentRef): msg is SimpleComponentDef {\n const uMsg = unref(msg);\n return typeof uMsg === 'object' && 'componentName' in uMsg;\n}\n\nexport function isCallableFunction(msg?: RenderContentRef): msg is RenderContentCallable {\n return typeof unref(msg) === 'function';\n}\n\n/**\n * Base validation error class with component rendering capabilities\n */\n\nexport class ValidationError {\n get componentName() {\n return 'Comment';\n }\n\n get componentBindings() {\n return {};\n }\n\n get componentBody() {\n return '';\n }\n\n get extraClasses(): ClassTypes {\n return '';\n }\n}\n\n/**\n * Simple text-only ValidationError\n */\nexport class ValidationErrorText extends ValidationError {\n constructor(\n public text: string,\n public classes: ClassTypes = '',\n ) {\n super();\n }\n\n get componentName() {\n return 'template';\n }\n\n get componentBody() {\n return this.text;\n }\n\n get extraClasses() {\n return this.classes;\n }\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(\n text: RenderContentRef,\n public classes: ClassTypes = '',\n ) {\n super();\n this.text = text;\n this.textType = computed(() => this.getTextType);\n }\n\n get resolvedText() {\n const text = unref(this.text);\n return isCallableFunction(text) ? text() : text;\n }\n\n get getTextType() {\n const msg = this.resolvedText;\n\n if (!msg) return 'string';\n if (msg instanceof MdString) return 'md';\n if (isSimpleComponentDef(msg)) return 'component';\n return 'string';\n }\n\n get componentName() {\n switch (unref(this.textType)) {\n case 'string':\n return 'template';\n case 'md':\n return 'vue-markdown';\n case 'component':\n return (this.resolvedText as SimpleComponentDef).componentName;\n default:\n return 'template';\n }\n }\n\n get componentBindings() {\n switch (unref(this.textType)) {\n case 'string':\n return {};\n case 'md': {\n const text = this.resolvedText as MdString;\n return { source: text.toString(), options: text.options, plugins: text.plugins };\n }\n case 'component':\n return (this.resolvedText as SimpleComponentDef).componentProps || {};\n default:\n return {};\n }\n }\n\n get componentBody() {\n switch (unref(this.textType)) {\n case 'string':\n return this.resolvedText as string;\n case 'component':\n return (this.resolvedText as SimpleComponentDef).componentVHtml || '';\n default:\n return '';\n }\n }\n\n get extraClasses() {\n return this.classes;\n }\n}\n\n/**\n * A value, renderable three different ways (plain text, markdown, component) - alias for ValidationErrorRenderContent\n */\nexport class RenderableValue extends ValidationErrorRenderContent {}\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';\nimport { unref } from 'vue';\n\nimport { ValueChangedAction } from '../actions/value-changed-action';\nimport { type FieldBase } from '../field-base';\nimport { FieldActionExecute, type IField } from '../field.interface';\n\nimport {\n isCallableFunction,\n isSimpleComponentDef,\n MdString,\n RenderContentRef,\n ValidationError,\n} from './validation-error';\n\nexport type ValidationFunctionResult = ValidationError[] | null;\nexport type ValidationFunction<T = any> = (\n newValue: T,\n oldValue: T,\n field: IField<T>,\n) => ValidationFunctionResult | Promise<ValidationFunctionResult>;\n\ninterface SourceProp {\n source: symbol;\n}\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((e) =>\n 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-expect-error validatingCount is protected, but we want it internally\n field.validating = ++(<FieldBase>field).validatingCount > 0;\n errors\n .then((err) => processErrors(err))\n .finally(() => {\n // @ts-expect-error validatingCount is protected, but we want it internally\n (<FieldBase>field).validatingCount = Math.max(0, (<FieldBase>field).validatingCount - 1);\n // @ts-expect-error validatingCount is protected, but we want it internally\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 static get classIdentifier() {\n return ValidatorClassIdentifier;\n }\n\n get eager() {\n return true;\n }\n\n protected replacePlaceholdersFunction(text: RenderContentRef, replace: Record<string, any>): RenderContentRef {\n return () => {\n let ret = unref(text);\n while (isCallableFunction(ret)) {\n ret = unref(this.replacePlaceholders(ret(), replace));\n }\n return ret;\n };\n }\n\n protected replacePlaceholders(text: RenderContentRef, replace: Record<string, any>): RenderContentRef {\n if (isCallableFunction(text)) return this.replacePlaceholdersFunction(text, replace);\n\n if (isSimpleComponentDef(text)) return text;\n let ret = unref(text) as string | MdString;\n Object.keys(replace).forEach((key) => {\n ret = ret.replaceAll(`{${key}}`, replace[key]);\n });\n return text instanceof MdString ? new MdString(ret.toString(), text.options, text.plugins) : 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","import 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 = (field: IField, currentResult: boolean, previousResult: boolean | undefined) => 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 static get classIdentifier() {\n return ConditionalStatementActionClassIdentifier;\n }\n\n get eager() {\n return true;\n }\n\n boundToField(field: IField) {\n this.boundFields.add(field);\n }\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\n// eslint-disable-next-line @typescript-eslint/no-namespace, no-redeclare\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","import 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 constructor(executorFn: (field: IField, supr: FieldActionExecute, newValue: boolean, oldValue: boolean) => boolean) {\n super(executorFn);\n }\n\n static get classIdentifier() {\n return EnabledChangingActionClassIdentifier;\n }\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 constructor(executorFn: (field: IField, supr: FieldActionExecute, newValue: boolean, oldValue: boolean) => void) {\n super(executorFn);\n }\n\n static get classIdentifier() {\n return EnabledChangedActionClassIdentifier;\n }\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\nexport class ValidChangedAction extends FieldActionBase {\n constructor(executorFn: (field: IField, supr: FieldActionExecute, newValue: boolean, oldValue: boolean) => void) {\n super(executorFn);\n }\n\n static get classIdentifier() {\n return ValidChangedActionClassIdentifier;\n }\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 DisplayMode from '@/display-mode';\nimport { FieldActionExecute, type IField } from '@/field.interface';\n\nconst VisibilityChangingActionClassIdentifier = Symbol('VisibilityChangingAction');\n\nexport class VisibilityChangingAction extends FieldActionBase {\n constructor(\n executorFn: (field: IField, supr: FieldActionExecute, newValue: DisplayMode, oldValue: DisplayMode) => DisplayMode,\n ) {\n super(executorFn);\n }\n\n static get classIdentifier() {\n return VisibilityChangingActionClassIdentifier;\n }\n\n execute(field: IField, supr: FieldActionExecute, newValue: DisplayMode, oldValue: DisplayMode): DisplayMode {\n return super.execute(field, supr, newValue, oldValue);\n }\n}\n\nconst VisibilityChangedActionClassIdentifier = Symbol('VisibilityChangedAction');\n\nexport class VisibilityChangedAction extends FieldActionBase {\n constructor(\n executorFn: (field: IField, supr: FieldActionExecute, newValue: DisplayMode, oldValue: DisplayMode) => void,\n ) {\n super(executorFn);\n }\n\n static get classIdentifier() {\n return VisibilityChangedActionClassIdentifier;\n }\n\n execute(field: IField, supr: FieldActionExecute, newValue: DisplayMode, oldValue: DisplayMode): 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\nexport abstract class FieldBase<T = any> implements IField<T> {\n abstract get value(): T;\n abstract set value(newValue: T);\n\n abstract get touched(): boolean;\n abstract set touched(touched: boolean);\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 {\n return this._visibility;\n }\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 {\n return this._enabled;\n }\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(revalidate: boolean = false) {\n if (revalidate) this.actions.triggerEager(this, this.value, this.value);\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>>(actionClass: new (...args: any[]) => T2, ...params: any[]): 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;\n case Operator.NOT_EQUALS:\n return operand1 != operand2;\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\nexport class ExecuteAction extends FieldActionBase {\n constructor(executorFn: (field: IField, supr: FieldActionExecute, params: any) => any) {\n super(executorFn);\n }\n\n static get classIdentifier() {\n return ExecuteActionClassIdentifier;\n }\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\nexport class ListItemAddedAction extends FieldActionBase {\n constructor(executorFn: (field: IField, supr: FieldActionExecute, item: any, index: number) => void) {\n super(executorFn);\n }\n\n static get classIdentifier() {\n return ListItemAddedActionClassIdentifier;\n }\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\nexport class ListItemRemovedAction extends FieldActionBase {\n constructor(executorFn: (field: IField, supr: FieldActionExecute, item: any, index: number) => void) {\n super(executorFn);\n }\n\n static get classIdentifier() {\n return ListItemRemovedActionClassIdentifier;\n }\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 protected _touched: boolean = false;\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() {\n return this._value;\n }\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 get touched(): boolean {\n return this._touched;\n }\n\n set touched(touched: boolean) {\n this._touched = touched;\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' }).registerAction(\n new ValueChangedAction(() => {\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-expect-error: prevent TS from complaining how create method is not ok because its declaration differs from Fld'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>(params?: Partial<IFieldConstructorParams<T>>): 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","<template>\n <render />\n</template>\n\n<script setup lang=\"ts\">\n// This is a straight copy from vuetify-inputs, but I don't want all the baggage associated. df-table dowesn't need\n// all those dependencies (but I did have to add vue-markdown-render for this widget)\nimport { h, resolveComponent } from 'vue';\n\nimport { ClassTypes, ValidationError } from '../validators';\n\ninterface Props {\n message: string | ValidationError[];\n classes?: ClassTypes;\n}\n\nconst props = withDefaults(defineProps<Props>(), { classes: 'text-error' });\nconst md = resolveComponent('vue-markdown');\nconst htmlElements = new Set([\n 'div',\n 'span',\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'aside',\n 'nav',\n 'header',\n 'footer',\n 'main',\n 'figure',\n 'figcaption',\n 'blockquote',\n 'pre',\n 'code',\n 'em',\n 'strong',\n 'small',\n 'mark',\n 'del',\n 'ins',\n 'sub',\n 'sup',\n 'i',\n 'b',\n 'u',\n 's',\n 'a',\n 'img',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'option',\n 'label',\n 'form',\n 'table',\n 'tr',\n 'td',\n 'th',\n 'thead',\n 'tbody',\n 'tfoot',\n 'ul',\n 'ol',\n 'li',\n 'dl',\n 'dt',\n 'dd',\n]);\n\nconst render = () => {\n if (typeof props.message === 'string') return h('span', { class: props.classes }, props.message);\n const res: ReturnType<typeof h>[] = [];\n props.message.forEach((msg) => {\n switch (msg.componentName) {\n case 'template':\n res.push(h('div', { class: [props.classes, msg.extraClasses] }, msg.componentBody));\n break;\n case 'vue-markdown':\n if (typeof md === 'string') {\n console.warn(\n \"You are using markdown for messages-widget, but you haven't registered a vue-markdown component\",\n );\n res.push(h('div', { class: [props.classes, msg.extraClasses] }, (<any>msg.componentBindings).source));\n } else {\n const componentBindings = <any>msg.componentBindings;\n res.push(\n h(md, {\n class: [props.classes, msg.extraClasses, 'df-messages-widget-markdown'],\n source: componentBindings.source,\n options: componentBindings.options,\n plugins: componentBindings.plugins,\n }),\n );\n }\n break;\n default:\n res.push(\n h(\n htmlElements.has(msg.componentName.toLowerCase()) // only resolve if it's not a common html element\n ? msg.componentName\n : resolveComponent(msg.componentName),\n {\n class: [props.classes, msg.extraClasses],\n ...msg.componentBindings,\n ...(msg.componentBody ? { innerHTML: msg.componentBody } : {}),\n },\n ),\n );\n break;\n }\n });\n return res;\n};\n</script>\n\n<style>\n/* we would like there to be no margins for markdown at the very top and very bottom. the rest of it is fine */\n.df-messages-widget-markdown > :first-child,\n.df-messages-widget-markdown > :last-child {\n margin-top: 0 !important;\n padding-top: 0 !important;\n margin-bottom: 0 !important;\n padding-bottom: 0 !important;\n line-height: 1.35em !important; /* inspired by vitepress' aggressive settings */\n}\n</style>\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(this._fields, fieldName, {\n get() {\n return field;\n },\n configurable: false,\n 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' && flds !== null && 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(Object.entries(data).map(([key, value]) => [key, Field.create({ value })])),\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 touched(): boolean {\n return Object.values(this._fields).some((field) => field.touched);\n }\n\n set touched(touched: boolean) {\n Object.values(this._fields).forEach((field) => {\n field.touched = touched;\n });\n }\n\n get fullValue(): Record<string, any> {\n const value: Record<string, any> = {};\n Object.entries(this._fields).forEach(([name, field]) => {\n value[name] = field.fullValue;\n });\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 validate(revalidate: boolean = false) {\n if (revalidate) Object.values(this._fields).forEach((field) => field.validate(true));\n super.validate(revalidate);\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 || []), ...