Make underlying form a slot.

This commit is contained in:
Austin Smith
2025-10-03 08:48:48 -04:00
parent 86943c1a7f
commit 33c6f54dec

View File

@@ -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 { export class Declaform extends HTMLElement {
static observedAttributes = [ static observedAttributes = [
'customInputs', // Lets user specify additional inputs to be considered "fields" by the form. Example: <object-list> 'customInputs', // Lets user specify additional inputs to be considered "fields" by the form. Example: <object-list>
'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 _fields: Field[];
private _form: HTMLFormElement; private _form: HTMLFormElement | null;
constructor() { constructor() {
super(); super();
this.attachInternals(); this.attachInternals();
this.attachShadow({ mode: 'open' }); this.attachShadow({ mode: 'open' });
this.shadowRoot!.innerHTML = '<form><slot></slot></form>'; this.shadowRoot!.innerHTML = '<slot name="form"></slot>';
this._fields = []; this._fields = [];
this._form = this.shadowRoot!.querySelector('form')!; this._form = null;
} }
connectedCallback() { 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 <form>.')
}
this._form = formSlot.assignedElements()[0] as HTMLFormElement;
const customInputs = this.getAttribute('customInputs')?.split(',').map(x => x.trim()) ?? []; const customInputs = this.getAttribute('customInputs')?.split(',').map(x => x.trim()) ?? [];
const selectors = ['input', 'select'].concat(customInputs).map(x => `${x}[name]`).join(','); const selectors = ['input', 'select'].concat(customInputs).map(x => `${x}[name]`).join(',');
const inputs = Array.from(this.querySelectorAll(selectors)); const inputs = Array.from(this.querySelectorAll(selectors));
@@ -74,41 +131,3 @@ function isFieldElement(x: unknown): x is FieldElement {
return true; 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;
}
}