form-conditions
v0.1.0
Published
Framework-agnostic TypeScript engine for conditional form logic: visible, required, disabled and readonly states with Zod-friendly helpers.
Downloads
18
Maintainers
Readme
@kiselman/form-conditions
Framework-agnostic TypeScript engine for conditional form logic.
Use it to declaratively compute whether form fields should be visible, required, disabled or readonly based on current form values.
It works with any UI/form library and has a Zod-friendly helper:
- Vue / Nuxt
- React / Next.js
- Svelte
- Angular
- Vanilla JS
- VeeValidate
- React Hook Form
- Zod
Why
Dynamic forms often spread business rules across components, watchers, computed values and validators:
if (values.type === 'company' && !values.companyName) {
// required
}This package keeps conditional form logic declarative, reusable and testable.
Install
npm install @kiselman/form-conditionsZod is optional. Install it only if you use the Zod helper:
npm install zodBasic usage
import { evaluateFormConditions } from '@kiselman/form-conditions'
const states = evaluateFormConditions({
values: {
age: 17,
hasCompany: true
},
fields: [
{
name: 'parentEmail',
visibleWhen: {
field: 'age',
operator: 'lt',
value: 18
},
requiredWhen: {
field: 'age',
operator: 'lt',
value: 18
}
},
{
name: 'companyName',
visibleWhen: {
field: 'hasCompany',
operator: 'eq',
value: true
},
requiredWhen: {
field: 'hasCompany',
operator: 'eq',
value: true
}
}
]
})
console.log(states.parentEmail)
// {
// name: 'parentEmail',
// visible: true,
// required: true,
// disabled: false,
// readonly: false
// }Explain mode
Use explain: true to debug why a field is visible, required, disabled or readonly.
const states = evaluateFormConditions({
values: { age: 16 },
fields: [
{
name: 'parentEmail',
visibleWhen: { field: 'age', operator: 'lt', value: 18 }
}
],
options: {
explain: true
}
})
console.log(states.parentEmail.reasons?.visible)
// {
// type: 'field',
// passed: true,
// field: 'age',
// operator: 'lt',
// expectedValue: 18,
// actualValue: 16
// }Zod integration
The package does not replace Zod. It complements it.
Zod validates data shape and formats. @kiselman/form-conditions decides what is currently visible, required, disabled or readonly.
import { z } from 'zod'
import { withFormConditions } from '@kiselman/form-conditions/zod'
const baseSchema = z.object({
hasCompany: z.boolean(),
companyName: z.string().optional()
})
const schema = withFormConditions(baseSchema, {
fields: [
{
name: 'companyName',
visibleWhen: { field: 'hasCompany', operator: 'eq', value: true },
requiredWhen: { field: 'hasCompany', operator: 'eq', value: true }
}
],
message: (field) => `${field.name} is required`
})
const result = schema.safeParse({
hasCompany: true,
companyName: ''
})
console.log(result.success) // falseReusable engine
import { createFormConditions } from '@kiselman/form-conditions'
const engine = createFormConditions([
{
name: 'vatId',
visibleWhen: { field: 'type', operator: 'eq', value: 'company' },
requiredWhen: { field: 'type', operator: 'eq', value: 'company' }
}
])
const states = engine.evaluate({ type: 'company' })
const issues = engine.requiredIssues({ type: 'company', vatId: '' })Supported operators
| Operator | Meaning |
| --- | --- |
| eq | actual value equals expected value |
| neq | actual value does not equal expected value |
| gt | greater than |
| gte | greater than or equal |
| lt | less than |
| lte | less than or equal |
| in | actual value is included in expected array |
| notIn | actual value is not included in expected array |
| contains | string or array contains expected value |
| startsWith | string starts with expected string |
| endsWith | string ends with expected string |
| matches | string matches RegExp or RegExp string |
| exists | value is not null or undefined |
| empty | value is empty string, empty array, empty object, null or undefined |
| notEmpty | value is not empty |
| between | number is between [min, max] inclusive |
Groups
const condition = {
all: [
{ field: 'type', operator: 'eq', value: 'company' },
{
any: [
{ field: 'country', operator: 'eq', value: 'LV' },
{ field: 'country', operator: 'eq', value: 'EE' }
]
}
]
}Aliases are also supported:
{ and: [...] }
{ or: [...] }
{ not: { field: 'age', operator: 'gte', value: 18 } }TypeScript
The package is written in TypeScript and ships declaration files.
import type { ConditionalField, FormConditionStates } from '@kiselman/form-conditions'Development
npm install
npm run typecheck
npm test
npm run buildPublish
npm login
npm run build
npm publish --access publicFor the first publish of a scoped package, --access public is important.
License
MIT
