From 33c6f54decfedcfab7353331d059a4695d8b7953 Mon Sep 17 00:00:00 2001 From: Austin Smith Date: Fri, 3 Oct 2025 08:48:48 -0400 Subject: [PATCH] Make underlying form a slot. --- src/declaform/declaform.ts | 101 ++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/src/declaform/declaform.ts b/src/declaform/declaform.ts index 34ea9ce..8d679ef 100644 --- a/src/declaform/declaform.ts +++ b/src/declaform/declaform.ts @@ -1,23 +1,80 @@ +/** + * This is the object that ties a group of inputs to a name. + * It assumes that every input in the group is of the same type. + */ +class Field { + + private _type: string; + + constructor(private name: string, private _inputs: FieldElement[]) { + if (this._inputs.length === 0) { + throw new Error("[Declaform] Error: cannot construct Field with empty inputs array.") + } + this._type = this._inputs[0]!.type; + } + + get value() { + switch(this._type) { + case 'checkbox': + return (this._inputs as HTMLInputElement[]).filter(x => x.checked).map(x => x.value); + case 'radio': + return (this._inputs as HTMLInputElement[]).find(x => x.checked)?.value; + default: + case 'date': + case 'datatime-local': + case 'email': + case 'number': + case 'password': + case 'select': + case 'text': + // can assert this._inputs[0]! because we throw Error in constructor if _inputs.length = 0 + return (this._inputs.length > 1) ? this._inputs.map(x => x.value) : this._inputs[0]!.value; + } + } + + set value(x: any) { + } + + get type() { + return this._type; + } +} + export class Declaform extends HTMLElement { static observedAttributes = [ 'customInputs', // Lets user specify additional inputs to be considered "fields" by the form. Example: + 'src', // defines api endpoint used to hydrate this object + 'action', // defines api endpoint used to submit this object to + 'method', // defines http method to use on "action" endpoint ] + /* The character that gets used to join multiple field values */ + static joining_char = ','; + private _fields: Field[]; - private _form: HTMLFormElement; + private _form: HTMLFormElement | null; constructor() { super(); this.attachInternals(); this.attachShadow({ mode: 'open' }); - this.shadowRoot!.innerHTML = '
'; + this.shadowRoot!.innerHTML = ''; this._fields = []; - this._form = this.shadowRoot!.querySelector('form')!; + this._form = null; } connectedCallback() { + const formSlot = this.shadowRoot?.querySelector('slot[name="form"]') as HTMLSlotElement; + if (!formSlot) { + throw new Error('[Declaform] Error: "form" slot is unassigned.') + } + if (formSlot.assignedElements()[0]?.tagName !== 'FORM') { + throw new Error('[Declaform] Error: "form" slot must be assigned to a
.') + } + this._form = formSlot.assignedElements()[0] as HTMLFormElement; + const customInputs = this.getAttribute('customInputs')?.split(',').map(x => x.trim()) ?? []; const selectors = ['input', 'select'].concat(customInputs).map(x => `${x}[name]`).join(','); const inputs = Array.from(this.querySelectorAll(selectors)); @@ -73,42 +130,4 @@ function isFieldElement(x: unknown): x is FieldElement { } return true; -} - -/** - * This is the object that ties a group of inputs to a name. - * It assumes that every input in the group is of the same type. - */ -class Field { - - /* The character that gets used to join multiple field values */ - static joining_char = ','; - - private _type: string; - - constructor(private name: string, private _inputs: FieldElement[]) { - if (this._inputs.length === 0) { - throw new Error("[Declaform] Error: cannot construct Field with empty inputs array.") - } - this._type = this._inputs[0]!.type; - } - - get value() { - switch(this._type) { - default: - case 'date': - case 'datatime-local': - case 'email': - case 'number': - case 'text': - return this._inputs.map(x => x.value); - } - } - - set value(x: any) { - } - - get type() { - return this._type; - } } \ No newline at end of file