/** * Bridge between reactive {@link Form} / {@link FormField} objects and the DOM. * * Provides `bindField()` to two-way-bind a single field to an input element, * and `bindForm()` to wire an entire `
` element to a {@link Form}. * * @module bquery/forms */ import { effect } from '../reactive/index'; import type { BindFieldOptions, BindFormOptions, Form, FormField } from './types'; const isInput = (el: Element): el is HTMLInputElement => el.tagName === 'INPUT'; const isTextarea = (el: Element): el is HTMLTextAreaElement => el.tagName === 'TEXTAREA'; const isSelect = (el: Element): el is HTMLSelectElement => el.tagName === 'SELECT'; const isButton = (el: Element): el is HTMLButtonElement => el.tagName === 'BUTTON'; const defaultGetValue = (element: Element): unknown => { if (isInput(element)) { if (element.type === 'checkbox') return element.checked; if (element.type === 'radio') return element.checked ? element.value : undefined; if (element.type === 'number' || element.type === 'range') { return element.value === '' ? '' : Number(element.value); } if (element.type === 'file') return element.files; if (element.type === 'date' || element.type === 'datetime-local' || element.type === 'time') { return element.value; } return element.value; } if (isTextarea(element)) { return element.value; } if (isSelect(element)) { if (element.multiple) { return Array.from(element.selectedOptions).map((o) => o.value); } return element.value; } if ((element as HTMLElement).isContentEditable) { return (element as HTMLElement).textContent ?? ''; } const anyEl = element as unknown as { value?: unknown }; return anyEl.value; }; const writeValue = (element: Element, value: unknown): void => { if (isInput(element)) { if (element.type === 'checkbox') { element.checked = Boolean(value); return; } if (element.type === 'radio') { element.checked = element.value === String(value); return; } if (element.type === 'file') return; element.value = value == null ? '' : String(value); return; } if (isTextarea(element)) { element.value = value == null ? '' : String(value); return; } if (isSelect(element)) { if (element.multiple && Array.isArray(value)) { const set = new Set(value.map((v) => String(v))); for (const option of Array.from(element.options)) { option.selected = set.has(option.value); } return; } element.value = value == null ? '' : String(value); return; } if ((element as HTMLElement).isContentEditable) { (element as HTMLElement).textContent = value == null ? '' : String(value); return; } try { (element as unknown as { value: unknown }).value = value; } catch { element.setAttribute('value', value == null ? '' : String(value)); } }; /** * Two-way bind a reactive {@link FormField} to a DOM input element. * * Supports `` (text, number, checkbox, radio, file, date), `