/** * What a time field will accept, and what it does when the user goes past the end. * * A time picker has three numbers and every one of them has a range that depends on the format: * an hour on a 12-hour clock runs 1–12, the same hour on a 24-hour clock runs 0–23, and a minute is * 0–59 on both. Those bounds were previously spread across the renderers as literals and enforced * by returning `null` — which reads to the caller as "nothing happened", indistinguishable from a * no-op, so an out-of-range entry was silently dropped rather than shown to be wrong. * * Two behaviours, deliberately different, because the user means different things: * * - **Stepping wraps.** Arrow-key or spinner movement is sequential, so 12 + 1 is 1 and 0 − 1 is 23. * A user holding the up arrow is scanning the range, not asserting a value, and stopping dead at * the end is the wrong answer to what they are doing. * - **Typing is judged.** A typed `25` or `61` is a claim about a specific time, and the honest * response is to say it is not one — not to clamp it to 23, which silently changes what was * asked for, and not to ignore it, which leaves the field looking accepted. * * Both halves are stated here, once, so a renderer cannot enforce one of them and forget the other. */ import type { MdyTimeFormat } from "@modyra/core/datetime"; import { type MdyTimeSteps } from "./time-granularity.js"; /** The three numbers a time is made of. */ export type MdyTimeField = "hour" | "minute"; export interface MdyTimeFieldBounds { /** Lowest accepted value, inclusive. */ readonly min: number; /** Highest accepted value, inclusive. */ readonly max: number; /** Distance between offered values. `1` is every one, which is what no granularity means. */ readonly step: number; } /** * The range a field accepts in a given format. * * The hour is the only one that moves: `1–12` with a period beside it, `0–23` without. A minute is * 0–59 whatever the clock says, which is the rule most often lost when bounds live as literals next * to the hour's. */ export declare function timeFieldBounds(field: MdyTimeField, format: MdyTimeFormat, steps?: MdyTimeSteps): MdyTimeFieldBounds; /** Why a typed value was not accepted. */ export type MdyTimeRejection = "not-a-number" | "out-of-range" | "off-step"; export type MdyTimeEntry = { readonly type: "accepted"; readonly value: number; } | { readonly type: "rejected"; readonly reason: MdyTimeRejection; readonly bounds: MdyTimeFieldBounds; }; /** * Judge a typed value against the field's range. * * Returns a rejection rather than a clamp or a `null`. A caller that clamps has answered a * different question from the one the user asked, and a caller that returns nothing leaves a field * that looks accepted holding a value it never took. */ export declare function acceptTimeField(field: MdyTimeField, format: MdyTimeFormat, raw: string | number, steps?: MdyTimeSteps): MdyTimeEntry; export declare function stepTimeField(field: MdyTimeField, format: MdyTimeFormat, current: number, delta: number, steps?: MdyTimeSteps): number;