diff --git a/src/ui/form-modal.ts b/src/ui/form-modal.ts index e44812d..f1a428c 100644 --- a/src/ui/form-modal.ts +++ b/src/ui/form-modal.ts @@ -2,6 +2,7 @@ import { App, Modal, TFile, Notice, FuzzySuggestModal } from 'obsidian'; import { FormDefinition } from '../types'; import { renderField, RenderedField, FieldValue } from './field-renderers'; import { validateForm, ValidationError } from '../utils/validators'; +import { computeVisibility } from '../utils/condition-engine'; import { FormProcessor } from '../core/form-processor'; // --------------------------------------------------------------------------- @@ -37,6 +38,7 @@ export class FilePickerModal extends FuzzySuggestModal { export class FormModal extends Modal { private form: FormDefinition; private renderedFields: Map = new Map(); + private fieldContainers: Map = new Map(); private targetFile: TFile | undefined; constructor(app: App, form: FormDefinition, targetFile?: TFile) { @@ -109,17 +111,25 @@ export class FormModal extends Modal { // Render each field for (const field of this.form.fields) { + const fieldWrapper = fieldsEl.createDiv({ cls: 'ff-field-wrapper' }); const initial = existingFrontmatter[field.id] as FieldValue | undefined; const defaultVal = field.defaultValue as FieldValue | undefined; const rendered = renderField( this.app, - fieldsEl, + fieldWrapper, field, initial ?? defaultVal, ); this.renderedFields.set(field.id, rendered); + this.fieldContainers.set(field.id, fieldWrapper); } + // Attach change listeners for reactivity + this.attachChangeListeners(fieldsEl); + + // Initial visibility evaluation + this.reevaluateVisibility(); + // Footer const footerEl = contentEl.createDiv({ cls: 'ff-form-footer' }); const submitText = @@ -157,8 +167,20 @@ export class FormModal extends Modal { }); } - private async handleSubmit(): Promise { - // Collect values + private reevaluateVisibility(): void { + const values = this.collectValues(); + const visibility = computeVisibility(this.form.fields, values); + + for (const field of this.form.fields) { + const container = this.fieldContainers.get(field.id); + if (!container) continue; + + const isVisible = visibility.get(field.id) !== false; + container.style.display = isVisible ? '' : 'none'; + } + } + + private collectValues(): Record { const values: Record = {}; for (const field of this.form.fields) { const rendered = this.renderedFields.get(field.id); @@ -166,6 +188,29 @@ export class FormModal extends Modal { values[field.id] = rendered.getValue(); } } + return values; + } + + private attachChangeListeners(container: HTMLElement): void { + container.addEventListener('input', () => this.reevaluateVisibility()); + container.addEventListener('change', () => this.reevaluateVisibility()); + container.addEventListener('click', () => { + // Defer to let toggle/rating state update first + requestAnimationFrame(() => this.reevaluateVisibility()); + }); + } + + private async handleSubmit(): Promise { + // Collect values (only from visible fields) + const allValues = this.collectValues(); + const visibility = computeVisibility(this.form.fields, allValues); + + const values: Record = {}; + for (const field of this.form.fields) { + if (visibility.get(field.id) !== false) { + values[field.id] = allValues[field.id]; + } + } // Clear previous errors for (const rendered of this.renderedFields.values()) { @@ -173,7 +218,10 @@ export class FormModal extends Modal { } // Validate - const errors: ValidationError[] = validateForm(this.form.fields, values); + const visibleFields = this.form.fields.filter( + (f) => visibility.get(f.id) !== false, + ); + const errors: ValidationError[] = validateForm(visibleFields, values); if (errors.length > 0) { for (const err of errors) { const rendered = this.renderedFields.get(err.fieldId); @@ -207,5 +255,6 @@ export class FormModal extends Modal { onClose(): void { this.contentEl.empty(); this.renderedFields.clear(); + this.fieldContainers.clear(); } }