/** * What a widget looks like, said in a way no renderer can influence. * * Milestone C asks whether three renderers given the same schema, the same initial state and the * same actions produce the same observation. Comparing markup would answer a different question: * `outerHTML` equality promotes every wrapper and every attribute order into public API, and would * make the adapters equal by forbidding them to differ at all. * * So the comparison runs over this reduction instead. Two rules keep it honest: * * - **Nothing here may know which adapter it is looking at.** Parts are found by the classes the * contract gives them, never by an adapter's own selectors. A snapshot that needed to be told * would not be canonical, and the suite built on it would be three suites. * - **No ids.** Every adapter generates its own, so a relationship is recorded as the *part* an * attribute lands on. That an id matches is an implementation detail; that the label points at the * control is the contract. */ import { type MdyWidgetKind } from "../catalog.js"; /** One part, as the contract can describe it without naming a framework. */ export interface MdyCanonicalPart { readonly part: string; /** The semantic the contract declares for it. */ readonly element: string; /** Its role, explicit or implicit. `null` when it carries none. */ readonly role: string | null; /** How many of it are on screen. A repeated part may have several; anything else must have one. */ readonly count: number; } /** One reference, resolved to the part it names rather than to an id. */ export interface MdyCanonicalRelationship { readonly from: string; readonly attribute: string; /** The part the reference resolves to, or `null` when it resolves to nothing. */ readonly to: string | null; } /** Whether the widget's overlay exists, and if so whether it is showing. */ export type MdyCanonicalOverlay = "absent" | "closed" | "open"; /** * A `focusOwner` expectation meaning "some part of this widget, and the contract does not say which". * * The difference between a requirement and an over-specification: after an overlay closes, focus * landing on the document body is always wrong, and which part catches it is the renderer's design. */ export declare const MDY_FOCUS_WITHIN = "somewhere in the widget"; export interface MdyCanonicalSnapshot { readonly kind: string; readonly parts: readonly MdyCanonicalPart[]; readonly relationships: readonly MdyCanonicalRelationship[]; /** The field states the root reflects, in the contract's own vocabulary. */ readonly state: readonly string[]; /** The value the form holds. Supplied by the caller: it is not a fact about the DOM. */ readonly value: unknown; /** The part that owns focus, or `null` when focus is outside the widget. */ readonly focusOwner: string | null; readonly overlay: MdyCanonicalOverlay; } export interface MdyCanonicalOptions { /** The value the form holds for this field. */ readonly value?: unknown; /** * Where a portalled popup went, when it is not inside the root. * * An adapter may lift its overlay to the document, which is a legitimate choice the contract does * not constrain — but a snapshot that could not see it would report every portalled popup as * absent and call two identical widgets different. */ readonly portalRoots?: readonly Element[]; } /** * Reduces a mounted widget to what the contract can say about it. * * The root is the element the adapter mounted; everything else is found from the catalogue. */ export declare function canonicalWidgetSnapshot(root: Element, kind: MdyWidgetKind, options?: MdyCanonicalOptions): MdyCanonicalSnapshot; /** * What every renderer must observably produce for a kind at rest. * * Declared once, here, rather than per adapter: the point of Milestone C is one expectation answered * by three renderers, and an expectation written inside an adapter's suite is three expectations that * happen to agree today. * * `parts` is what every renderer actually shows at rest, measured across all three rather than * reasoned about: an empirical floor, so one dropping a part is visible even where the contract * would permit it. `optional` is every other part the kind declares — presence there depends on a * free choice (eager or lazy mounting) or on what the consumer supplied (a supporting text, a * prefix, a custom option template), and two renderers disagreeing about one are both right. * * `aria-describedby` is deliberately absent from every `relationships` list. At rest, with no errors * and nothing supplied to describe, what it names depends on whether a renderer materialises an * empty description box — one does, another does not, and both conform. It becomes normative once * there is something to say, which belongs to the invalid state's expectation rather than this one. */ export interface MdyCanonicalExpectation { /** Parts every renderer shows at rest. */ readonly parts: readonly string[]; /** Parts a renderer may show or not, because the contract leaves the strategy free. */ readonly optional: readonly string[]; readonly relationships: readonly MdyCanonicalRelationship[]; readonly overlay: MdyCanonicalOverlay; /** * The states the widget reflects, in the contract's own vocabulary, order-insensitive, and absent * when the contract does not constrain them. * * Absent only where renderers make different defensible choices — whether abandoning an * interaction counts as having touched the field is a product decision, not a rendering one. */ readonly state?: readonly string[]; /** * The value the field holds, and absent when the contract cannot name it. * * Absent only where no shared table could: a file field's filled value is a `File`, and two files * with the same bytes are still different values, so each fixture makes its own. */ readonly value?: unknown; /** * The part focus rests on, `null` when focus is nowhere in the widget, and absent when the * contract does not constrain it. * * Left absent only where two renderers make different, defensible choices — a combobox may keep * focus on its input and drive the list with `aria-activedescendant`, or move focus into a search * field, and both are documented patterns. Freezing one of them here would buy agreement by * forbidding a legitimate implementation, which is the failure this contract is written to avoid. */ readonly focusOwner?: string | null; } /** * The empty value of each kind: what a field holds before anyone has given it one. * * One table, consumed by every adapter's fixture, because "the same initial state" is half of what * Milestone C compares. Three fixtures that each decide for themselves are three different * questions: a number field started at `0` is filled and valid, one started at `null` is empty and * required-failing, and the two renderers were never asked the same thing. * * Not derivable from `MDY_VALUE_CONTRACTS`, which says a kind's shape and whether it is nullable — * `null` and `[]` are both legal for a multiselect and only one of them is what an untouched field * holds. */ export declare const MDY_CANONICAL_EMPTY: Readonly>>; export declare const MDY_CANONICAL_AT_REST: Readonly>>; /** * What every renderer must observably produce for a kind the user has left invalid. * * Derived from the resting expectation rather than restated, because the invalid state *is* the * resting one plus what invalidity adds: the error list, the reference that names it, and the two * states the field now reflects. A second hand-written table would drift from the first the moment * a part moved, and the drift would look like a renderer defect. * * The three things invalidity changes: * * - **`errors` and `errorItem` stop being optional.** At rest a renderer may or may not materialise * an empty list and both conform; once there is an error to show, a renderer that shows none is * not making a free choice. * - **`aria-describedby` becomes normative.** At rest it may name an empty description box, or * nothing, depending on whether the renderer builds one. Here it must reach the error list, or the * error is rendered, styled, and announced to nobody. * - **The field reflects `invalid` and `touched`.** `invalid` because it is; `touched` because a * field the user has not reached is not one they are being told is wrong. */ export declare const MDY_CANONICAL_INVALID: Readonly>>; /** * What every renderer must produce for a kind the form has disabled. * * Rest plus one state, and deliberately nothing else. A disabled field that is also required and * empty is two states at once, and a renderer getting either wrong would be reported the same way — * so this is measured against a field no validator has judged, like rest itself. */ export declare const MDY_CANONICAL_DISABLED: Readonly>>; /** * What every renderer must produce for a kind whose overlay is open. * * Only the kinds that have one: a text field has no open state to disagree about. * * `popup` stops being optional here. At rest a renderer may mount it eagerly or build it on demand * and both conform; once the widget is open, a renderer showing no popup is not making a free * choice. */ export declare const MDY_CANONICAL_OPEN: Readonly>>; /** * The value each kind holds once something has been put in it. * * The counterpart to `MDY_CANONICAL_EMPTY`, and needed for the same reason: "the same actions" means * nothing unless every renderer is handed the same value to render. */ export declare const MDY_CANONICAL_FILLED: Readonly>>; /** * What every renderer must produce for a kind holding a value it was given from outside. * * The roadmap's *programmatic update*: a value set by the form rather than typed by the user. It is * the same widget as at rest with something in it, and the only anatomical difference measured * across the catalogue is the select's — a filled select shows its value, so the placeholder that * stands in for one becomes optional rather than required. * * No state is reflected. Putting a value in a field is not the user touching it, and a renderer that * marked it touched would show validation for an interaction that never happened. */ export declare const MDY_CANONICAL_FILLED_OBSERVATION: Readonly>>; /** * What every renderer must produce once an open overlay has been dismissed from the keyboard. * * The first expectation in this file that describes the result of an *action* rather than a state * something was put into. Opening a widget and pressing Escape is one gesture every overlay kind * supports, and the contract declares the transition; this is what the widget must look like * afterwards. * * **Focus returns into the widget**, and the contract says no more than that. A dismissed overlay * that leaves focus on the document body drops the user at the top of the page with no way back to * where they were, which is the one outcome closing a popup must never have. *Which* part receives * it is a design choice: the opener the user activated is the obvious answer, and a range picker * putting them back in its start field so they can keep typing is a defensible one. * * **Opening a panel and closing it is an act on the value.** It is the panel's version of typing and * deleting: the person saw what was on offer and took none of it, which is a decision, where merely * passing through the field is not. So the state here is the resting one plus `touched` — and not * `dirty`, because nothing about the value changed. * * This reverses what this table said before, and the reversal is the point. It read "abandoning an * interaction does not touch the field", which is the right answer to a different question: a person * who *tabs past* a required field has decided nothing, and that is now answered where it belongs — * a bare traversal marks nothing at all, in any kind. Once traversal is silent, the panel is no * longer the thing that might speak too early; it is the one gesture on these kinds that corresponds * to filling a box and clearing it again. ADR 0167. */ export declare const MDY_CANONICAL_AFTER_ESCAPE: Readonly>>; /** How a renderer's snapshot departs from the canonical expectation, in the contract's words. */ export declare function compareToCanonical(snapshot: MdyCanonicalSnapshot, expectation: MdyCanonicalExpectation): readonly string[];