docs: add conditional logic design document
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ae2285c229
commit
22eeccf022
1 changed files with 136 additions and 0 deletions
136
docs/plans/2026-02-13-conditional-logic-design.md
Normal file
136
docs/plans/2026-02-13-conditional-logic-design.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# Conditional Logic Design Document
|
||||
|
||||
**Date**: 2026-02-13
|
||||
**Status**: Approved
|
||||
**Plugin**: obsidian-formfire
|
||||
**Phase**: 3 — Conditional Field Visibility
|
||||
|
||||
## Problem
|
||||
|
||||
Forms with many fields become overwhelming when not all fields are relevant in every context. A "Meeting Notes" form shouldn't show "Book Author" fields. Currently all fields are always visible — users must skip irrelevant ones manually.
|
||||
|
||||
## Solution
|
||||
|
||||
Add per-field conditional visibility rules with AND/OR logic. Fields can reference other fields' values to determine whether they should be shown or hidden. Hidden fields are excluded from submission.
|
||||
|
||||
## Approach
|
||||
|
||||
**Rule-per-Field** — each `FormField` gets an optional `conditions` object containing rules and a logic combinator. This keeps conditions co-located with the field they control and avoids a separate rules data structure.
|
||||
|
||||
## Data Model
|
||||
|
||||
### New Types
|
||||
|
||||
```typescript
|
||||
type ConditionOperator =
|
||||
| 'equals' | 'not_equals' // all field types
|
||||
| 'contains' | 'not_contains' // text, textarea, tags
|
||||
| 'is_empty' | 'is_not_empty' // all field types
|
||||
| 'greater_than' | 'less_than'; // number, rating, slider
|
||||
|
||||
interface ConditionRule {
|
||||
fieldId: string; // reference to another field
|
||||
operator: ConditionOperator;
|
||||
value?: unknown; // not needed for is_empty/is_not_empty
|
||||
}
|
||||
|
||||
interface FieldConditions {
|
||||
logic: 'and' | 'or'; // how rules are combined
|
||||
rules: ConditionRule[]; // at least 1 rule
|
||||
}
|
||||
```
|
||||
|
||||
### FormField Extension
|
||||
|
||||
```typescript
|
||||
interface FormField {
|
||||
// ... existing fields ...
|
||||
conditions?: FieldConditions; // optional, undefined = always visible
|
||||
}
|
||||
```
|
||||
|
||||
### Operator Availability by Field Type
|
||||
|
||||
| Operator | text/textarea | number | toggle | date/time | dropdown | tags | note-link/folder | rating | slider | color |
|
||||
|----------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| equals / not_equals | yes | yes | yes | yes | yes | — | yes | yes | yes | yes |
|
||||
| contains / not_contains | yes | — | — | — | — | yes | — | — | — | — |
|
||||
| is_empty / is_not_empty | yes | yes | — | yes | yes | yes | yes | yes | yes | yes |
|
||||
| greater_than / less_than | — | yes | — | — | — | — | — | yes | yes | — |
|
||||
|
||||
Note: `toggle` doesn't support `is_empty` since it always has a value (true/false).
|
||||
|
||||
## Evaluation Engine
|
||||
|
||||
New module: `src/utils/condition-engine.ts`
|
||||
|
||||
```typescript
|
||||
evaluateConditions(
|
||||
conditions: FieldConditions,
|
||||
values: Record<string, unknown>
|
||||
): boolean
|
||||
```
|
||||
|
||||
### Logic
|
||||
|
||||
1. Each `ConditionRule` is evaluated against the current value of the referenced field
|
||||
2. Results are combined: `and` = all must be true, `or` = at least one must be true
|
||||
3. Return: `true` = show field, `false` = hide field
|
||||
|
||||
### Operator Semantics
|
||||
|
||||
- `equals` / `not_equals` — string coercion for comparison, strict for booleans
|
||||
- `contains` / `not_contains` — substring check for strings, element check for arrays (tags)
|
||||
- `is_empty` / `is_not_empty` — `null`, `undefined`, `""`, `[]` count as empty
|
||||
- `greater_than` / `less_than` — numeric comparison (parseFloat)
|
||||
|
||||
### Cycle Prevention
|
||||
|
||||
Fields can only reference fields that appear **before** them in the field list. This prevents circular dependencies by design and is enforced in the builder UI (field dropdown only shows preceding fields).
|
||||
|
||||
## Reactivity in Form Modal
|
||||
|
||||
1. **Change listeners** on every field — value changes trigger `reevaluateVisibility()`
|
||||
2. `reevaluateVisibility()` iterates all fields with `conditions`, evaluates them, toggles `display: none`
|
||||
3. **Hidden fields excluded from submit** — values not written to frontmatter
|
||||
4. **Validation skips hidden fields** — a required field that is hidden does not block submission
|
||||
|
||||
## Builder UI
|
||||
|
||||
### Per-Field Conditions Editor
|
||||
|
||||
Located below existing field settings, collapsible:
|
||||
|
||||
**Collapsed**: Shows summary like `"Show if: type = Meeting"` or `"Always visible"`
|
||||
|
||||
**Expanded**:
|
||||
1. **Logic dropdown** at top: `ALL conditions match` (AND) / `ANY condition matches` (OR)
|
||||
2. **Rule rows**, each containing:
|
||||
- Field dropdown (only fields before current field)
|
||||
- Operator dropdown (filtered by selected field's type)
|
||||
- Value input (type-dependent: text input, dropdown with options, number input, toggle; hidden for is_empty/is_not_empty)
|
||||
- Delete button (×)
|
||||
3. **"+ Add condition"** button
|
||||
|
||||
### Live Preview Behavior
|
||||
|
||||
Fields with unmet conditions are shown **grayed out with a "conditional" badge** in the builder preview (not fully hidden, so the builder user can see all fields exist).
|
||||
|
||||
## Files Changed
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/types.ts` | New types: `ConditionOperator`, `ConditionRule`, `FieldConditions`; add `conditions?` to `FormField` |
|
||||
| `src/utils/condition-engine.ts` | **New** — `evaluateConditions()`, operator logic, helpers |
|
||||
| `src/utils/validators.ts` | Accept visibility map, skip hidden fields |
|
||||
| `src/ui/form-modal.ts` | Change listeners, `reevaluateVisibility()`, exclude hidden fields from submit |
|
||||
| `src/ui/form-builder.ts` | Conditions editor UI per field (collapsed/expanded, rule editor) |
|
||||
| `src/ui/field-renderers.ts` | No changes — conditions operate one level above |
|
||||
| `styles.css` | Condition editor styles, conditional badge in preview, show/hide transitions |
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Nested condition groups (AND inside OR) — one level is sufficient
|
||||
- Form-level conditions (hide entire form)
|
||||
- Actions other than show/hide (e.g. set value, change required)
|
||||
- Migration — `conditions` is optional, existing forms work unchanged
|
||||
Loading…
Reference in a new issue