/** * How a kind's value leaves the page when the browser submits the form it sits in. * * A consumer puts these controls inside a `
` and gives the page a button that submits. It is * elementary HTML, and until this existed a native submit sent **nothing**: a control without a * `name` is not serialised, that is the rule, and fifteen of the seventeen kinds had no `name` at * all. The other two — `radio` and `segmented` — had one, and it was the scoped widget id rather * than the field's path, so the payload carried a generated key nobody had asked for. * * ## The name is per part, and the four shapes are not interchangeable * * Naming "the field's control" is not a thing a kind can be asked for, because the kinds disagree * about how many controls a value has and what they are: * * one control, and it is the value text, number, slider, datepicker, file, timepicker… * many controls, one shared name radio, segmented — this is how HTML groups them * many controls, one value between them daterange (two dates), colors (a picker and a hex box) * no control at all select, multiselect * * Writing the field's path onto every `input` in a kind's subtree gets three of the four wrong: it * sends a timepicker's hour and minute alongside the time itself, sends a daterange's two ends under * one key, and sends the popup's filter box — a control that holds no value and exists only while * the popup is open. **The part that carries the value is declared here, not deduced from shape.** * * ## Why a name is enough, and where it is not * * The grouping of radio buttons is by **form owner *and* name**, measured identical in Chromium, * Firefox and WebKit: two ``s each holding `name="colour"` are two independent groups, and * choosing in one leaves the other alone. So inside a form the path is both correct to submit and * safe to group by. * * Outside any form the same two sets merge into one — which is why the scoped id was there. But a * control outside a form is never submitted natively either, so there the name has no receiving end * and is only a grouping key. That is the whole of the tension, and it resolves itself: **the name * is the question, the form owner is who is answering.** */ import { type MdyWidgetKind } from "./catalog.js"; /** * How one kind puts its value into a form's payload. * * Every kind declares one. A kind that declared none would render controls a native submit silently * drops, which is the defect this contract exists to make impossible to reintroduce. */ export type MdySubmissionShape = /** One control, and it is the value. The part carries the field's name. */ { readonly form: "single"; readonly part: string; } /** * Many controls, one name between them, which is how HTML expresses a single choice: only the * checked one is serialised, and sharing the name is what makes the arrow keys move within the * set and Tab step past it as a whole. */ | { readonly form: "shared"; readonly part: string; } /** * A boolean, which HTML serialises in a way a reader cannot interpret without knowing the rule: an * unchecked box is **absent** from the payload, so `false` and "never sent" are the same thing, * and a checked box with no `value` attribute sends the string `on`. * * So the control carries the model's value explicitly, and a hidden companion carries the false * case ahead of it — the later key wins, so the companion is only read when the box is unchecked. */ | { readonly form: "boolean"; readonly part: string; readonly companion: string; } /** * Several parts that are one value between them, each sent under its own suffixed key. A single * key would send the same name twice and leave the receiving end unable to tell the ends apart. */ | { readonly form: "split"; readonly parts: readonly { readonly part: string; readonly suffix: string; }[]; } /** * No form control exists, so one is added that holds nothing but the value. * * `repeats` distinguishes a value from a list of them: a multiselect emits one per selected value, * in order, because a single joined key loses both the order and the multiplicity that the field's * whole purpose is to carry. */ | { readonly form: "hidden"; readonly repeats: boolean; }; /** How this kind puts its value into a form's payload. */ export declare function submissionFor(kind: MdyWidgetKind): MdySubmissionShape; /** * The key each part of a field sends its value under, given the field's path. * * The path and not the widget id: the id carries a per-form scope so that two forms on one page do * not collide, and a scope in a payload is a key the receiving end never asked for. Two forms send * the same key and stay separate, because a form's payload is its own. */ export declare function submissionNames(kind: MdyWidgetKind, path: string): Readonly>; /** * Every kind declares a shape, and every part it names is a part that kind actually has. * * Exported rather than asserted at import time: a contract that throws while loading takes the * library down over a defect an audit should have caught at build time. */ export declare function submissionDefects(): readonly string[]; /** * The hidden input a boolean field renders ahead of its box, as a part a renderer can bind. * * Here rather than in each renderer because all three need the same three attributes and none of * them needs to know why. The why is HTML's: an unchecked box is absent from the payload, so a * person who said no and a form that never carried the question arrive identical. */ export declare function submitFalsePart(path: string, state?: { readonly disabled?: boolean; readonly checked?: boolean; }): { readonly classes: readonly string[]; readonly attributes: Readonly>; }; /** * The name a group of radio inputs shares, which is two things at once. * * In HTML the `name` is what makes a set of radios one choice — arrow keys move within it, Tab steps * past it whole, and picking one clears the rest. It is *also* the key the value arrives under. Those * two jobs want different answers, and which one is at stake depends on where the control sits. * * Grouping is by **form owner and name together**, measured identical in Chromium, Firefox and * WebKit: two ``s each holding `name="colour"` are two independent groups, and a group with no * owner does not merge with one that has an owner either. So inside a form the path is safe to group * by and correct to submit. * * Outside every form the same two sets *do* merge — and there a native submit sends nothing at all, * so the name has no receiving end and is only a grouping key. That is where the scoped id earns its * place, and the only place it does. */ export declare function groupSubmitName(within: Element | null | undefined, path: string, scopedId: string): string; /** * Keeps a field's hidden submission inputs in step with the value it holds. * * Two kinds have no form control at all — a `select` is a button and a listbox, a `multiselect` is a * button and a strip of chips — so a native submit had nothing to read and sent nothing. This is the * only place in the library where the DOM carries a second copy of a value the model owns, and it is * here because there is no alternative: an input is the only thing a form serialises. * * One input per value, in order. A multiselect joined into a single key would lose both the order * and the multiplicity, which is the whole of what the field is for. * * The inputs are reused rather than replaced: rebuilding them on every change would discard nothing * a person can see, but it would churn the document on every keystroke of a filter. */ export declare function syncSubmitValues(host: Element, path: string, values: readonly unknown[], disabled?: boolean): void; /** * Writes each control's submission key onto the elements that carry it, found by the classes the * contract declares for that part. * * For the kinds whose value is spread over several controls — a range's two ends, a colour's picker * and its hex box — and for the kinds whose one control the shell already names, this is the single * place that decides which element gets which key. * * By class rather than by a reference the renderer passes: three renderers build these elements in * three different ways, and the classes are the one description all three already honour. A part * whose element is missing is skipped rather than raising — a renderer may legitimately draw fewer * elements than the contract permits. * * The elements not named by the shape are *cleared*, which is the load-bearing half: a colour's * native picker and a range's second end both inherit a name from the shared control projection, and * two controls under one key send the value twice. `FormData.get` then takes the first and drops the * rest without an error, so the loss is silent. */ export declare function applySubmissionNames(root: Element, kind: MdyWidgetKind, path: string): void;