feat: add reactive conditional visibility to form modal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c494a75242
commit
bfe4dc6d5e
1 changed files with 53 additions and 4 deletions
|
|
@ -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<TFile> {
|
|||
export class FormModal extends Modal {
|
||||
private form: FormDefinition;
|
||||
private renderedFields: Map<string, RenderedField> = new Map();
|
||||
private fieldContainers: Map<string, HTMLElement> = 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<void> {
|
||||
// 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<string, unknown> {
|
||||
const values: Record<string, unknown> = {};
|
||||
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<void> {
|
||||
// Collect values (only from visible fields)
|
||||
const allValues = this.collectValues();
|
||||
const visibility = computeVisibility(this.form.fields, allValues);
|
||||
|
||||
const values: Record<string, unknown> = {};
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue