<!-- GENERATED by scripts/build-llms.mjs from llms/utility.md — do not edit this file. -->

# `lr-known-date`

- **Import** `import '@aceshooting/lyra-ui/components/lr-known-date.js';` (stable tag alias; registers the tag)
- **Class** `LyraKnownDate`, also available unregistered from `@aceshooting/lyra-ui/components/utility/known-date/known-date.class.js`
- **Family** `components/utility/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecated part** `label` since `8.0.0`; use part `::part(form-control-label)`; removal not before `10.0.0` — The form-control-label part matches the shared form-control vocabulary; label remains on that same node during the compatibility window. That version is a policy floor, not a plan: `wa-known-date` still publishes its own deprecated `label` part, so this alias is removed only when upstream's is.
- **Optional peers** none
- **Themeable via** 17 parts, 13 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-known-date`

A form-associated control for a date the user already knows (a birthdate, a passport expiry),
collected as three plain day/month/year number fields in the locale's natural order rather than a
calendar popup. Uses the shared `FormAssociated` mixin; the submitted value is always canonical ISO
8601 (`YYYY-MM-DD`), or `''` while any field is blank or the combination isn't a real calendar date.

Host `aria-describedby` resolves external guidance onto the aggregate date fieldset and follows live
target replacement/removal/reinsertion and document adoption. Each native field retains the
component’s local hint and error guidance. Removed `label` and `hint` render as absent. Removing
`day-label`, `month-label`, or `year-label` restores that field’s localized omitted default; an
explicitly empty label remains empty. Own or fieldset disablement preserves each field’s resting
paint on hover and press.

**Properties:**

- `value: string` — canonical `YYYY-MM-DD` or `''`. Assignment goes through a strict-ISO gate:
  a non-zero-padded (`"2007-3-27"`) or calendar-invalid (`"2007-02-30"`) literal sanitizes to `''`
  and clears all three fields. Programmatic assignment never emits `input`/`change`
- `valueAsDate: Date | null` — the same value as a local-midnight `Date`; settable (assigning
  `null` clears)
- `parts: LyraKnownDateParts` — the live raw `{ day, month, year }` strings. Assigning a complete valid set
  synchronizes the canonical `value`; assigning an incomplete or impossible set clears `value`
- `valueInput: HTMLInputElement` — hidden native `type="date"` mirror kept synchronized with
  `value`, `min`, `max`, `required`, `disabled`, and `readonly` for integrations that inspect native
  date constraints
- `validationTarget: HTMLElement | undefined` (attribute: false) — the browser validation bubble's
  focus anchor; defaults to the first visible day/month/year field in locale order, never the
  hidden `valueInput` mirror. Settable to override which element the popup anchors to
- `min: string = ''`, `max: string = ''` — inclusive `YYYY-MM-DD` bounds, surfaced as
  `rangeUnderflow`/`rangeOverflow`
- `readonly: boolean = false` (reflected) — also suspends all validity flags
- `appearance: 'filled' | 'outlined' | 'filled-outlined' = 'outlined'` (reflected) — field fill and
  border treatment; `pill: boolean = false` (reflected) rounds every field fully
- `size: '2xs' | 'xs' | 's' | 'm' | 'l' | 'xl' = 'm'` (reflected) — control density on the library's
  shared six-step ladder, scaling each field's height floor, padding, font size and corner radius
  together so a birthdate field lines up with the `lr-input`/`lr-date-input` beside it at the same
  declared size. `'small'`/`'medium'`/`'large'` are accepted as exact synonyms of `'s'`/`'m'`/`'l'`,
  so migrating from an upstream that spells them that way needs no attribute rewrite. Every tier
  resolves to at least the 24px pointer-target floor, so even `'2xs'` stays usable
- `label: string = ''`, `hint: string = ''`, `errorText: string = ''` (attribute `error-text`)
- `locale: string = ''` — BCP-47 override for field order and per-field label sampling only
  (redeclared non-reflecting over the base `locale`, like `lr-date-input`)
- `autocomplete: string = ''` — the special value `'bday'` expands to
  `bday-day`/`bday-month`/`bday-year` across the three fields; `'on'` and `'off'` apply to all three,
  while any other non-empty field-specific token is forwarded only to the year field
- `withLabel: boolean = false` (`with-label`) and `withHint: boolean = false` (`with-hint`) — SSR
  slot-presence hints. Normal client rendering detects slotted label/hint content automatically
- `dayLabel?: string` (`day-label`), `monthLabel?: string` (`month-label`), `yearLabel?: string`
  (`year-label`) — visible **and** accessible per-field labels. `undefined` uses the corresponding
  localized default; every supplied string, including `''` and the old English copy, is
  caller-owned
- `accessibleLabel: string | null = null` (attribute `aria-label`) — applied to `[part="fieldset"]`,
  which owns the group role, overriding the `<legend>`-derived name
- The shared form surface adds `defaultValue`, `customError` (`custom-error`), `getForm()`,
  `checkValidity()`, `reportValidity()`, and `setCustomValidity(message)`.

**Static constructor API:** `LyraKnownDate.validators` is the mirrored callable validator catalog.
Each access returns a fresh `LyraFormValidator<LyraKnownDate>[]`; its entry observes
`required`/`disabled`/`readonly`/`value`/`min`/`max`, and `checkValidity(element)` projects the
element's current `ValidityState` into `{ isValid, message, invalidKeys }` without changing it.

```ts
import { LyraKnownDate } from "@aceshooting/lyra-ui/components/utility/known-date/known-date.js";

const knownDate = document.querySelector("lr-known-date")!;
const result = LyraKnownDate.validators[0].checkValidity(knownDate);
```

For mapped JavaScript/TypeScript compatibility, assigning `null` to `name` clears it to the
canonical `''` read value and removes the `name` attribute. The getter remains non-nullable.

**Methods:** `focus(options?)` focuses the first empty field in locale order (or the first field when
all are filled); `blur()` blurs whichever field currently has focus; `resetValidity()` clears a
consumer-supplied custom error and republishes intrinsic constraints;
`formStateRestoreCallback(state)` restores a string state and clears for other shapes.

**Events:** native bubbling/composed `InputEvent` `input` (every keystroke, preserving the private
field edit's `inputType`) and native
bubbling/composed `Event` `change` (a field blur where the composite value newly transitioned),
plus re-dispatched bubbling/composed `focus` and `blur` (`blur` fires once when focus
leaves all three fields, not per field-to-field Tab; each entry into the control likewise produces
exactly one public `focus`, with the private trusted focus suppressed). `input`/`change` detail is
`{ value, day, month, year, field }` — `value` is the canonical ISO date or `''`, `day`/`month`/`year`
are the live raw typed text, and `field` is `'day' | 'month' | 'year'`, whichever was last edited.
`lr-invalid` (no detail) is emitted once as a bubbling/composed alias when native validity fails.

**Slots:** `label`, `hint`, `error` (each rendered alongside its matching property).

**CSS parts:** `base`, `known-date`, and `form-control` are aliases on the same outer wrapper;
`fieldset` (the `<fieldset>` grouping the fields —
carries `aria-label` when `accessibleLabel` is set), `legend` (the `<legend>`; hidden when there is
no label, and grows a `*` suffix while `required`), `form-control-label` (`label` is its deprecated
compatibility alias), `fields` / `form-control-input` (aliases on the flex row), `field` (one field
block, repeated three times, `data-field="day"|"month"|"year"`) plus its matching `field-day`,
`field-month`, or `field-year` token, `field-input` (the native
`<input type="text" inputmode="numeric">` inside it, same `data-field` marker), `field-label` (the
small per-field text label), `hint`, `error` (visible non-live validation text and the fields'
`aria-describedby` target).

Once initial rendering and slot distribution settle, a newly visible or changed validation error
is appended exactly once to Lyra's shared assertive light-DOM announcement sink. Identical renders
are deduplicated, while clearing and later re-showing the same error announces it again. Initial
connection and reconnection do not replay an existing error. Hidden, inert, CSS-hidden, and
`aria-hidden` slotted error content is excluded; revealing meaningful error text is the change that
announces it. Within that error content, `display:none` and `content-visibility:hidden` prune a
branch; a `visibility:hidden|collapse` wrapper suppresses its own text while a descendant that
restores `visibility:visible` remains exposed. Updates also stay silent while the control or a
composed ancestor is hidden, then the current error announces if it becomes newly visible. This
tracking follows nested forwarding slots as well: mutations and reassignment of their flattened
assigned nodes update the announcement without requiring the wrapper component to re-render.

The `label` part alias was deprecated in 8.0.0 in favor of the shared form vocabulary
`form-control-label`. Both names remain on the same node during the compatibility window; use
`::part(form-control-label)` in new CSS. Removal is not scheduled: Web Awesome still publishes its
own deprecated `label` part on `<wa-known-date>`, and a mirrored tag owes its whole upstream
surface, so the alias goes only when upstream's does.

**The required marker.** The `*` the legend grows while `required` is the library's shared
required-field marker, and it takes the same three consumer-settable properties every other
labelled control in the library does: `--lr-form-control-required-content` (the glyph, as a quoted
CSS `content` string; `''` suppresses it), `--lr-form-control-required-color` (default
`var(--lr-color-danger)`) and `--lr-form-control-required-offset` (default `0`). One declaration on
an ancestor — `:root` included — retunes this marker along with every other one in the page. The
one detail specific to this component: `[part="legend"]` owns the marker, while
`[part="form-control-label"]` owns only the label content inside that legend. A
`::part(form-control-label)` rule therefore cannot retheme the marker; use the three public marker
properties above. With no label the legend is hidden and nothing is painted. Full description in
`llms/shared.md` → "The required-field marker".

**CSS states:** `:state(blank)` while the composite value is empty/incomplete;
`:state(disabled)` for direct or fieldset-cascaded disablement.

**Themeable custom properties:** `--lr-known-date-field-padding-block`,
`--lr-known-date-field-padding-inline`, `--lr-known-date-field-font-size` and
`--lr-known-date-field-min-height` all read the shared control ladder rather than a hand-kept copy
of the scale — respectively `--lr-form-control-padding-block`, `--lr-form-control-padding-inline`,
`--lr-form-control-font-size`, and `max(var(--lr-form-control-height), var(--lr-size-24px))`, each
of which the ladder re-points per `size` tier. That is what keeps the three fields the same height
as an `<lr-input>`/`<lr-date-input>` in the same form row at every tier; the
`--lr-known-date-field-*` names are unchanged and are still the documented override point. The
min-height resolves to 24px at `2xs`/`xs` (WCAG 2.2 SC 2.5.8's pointer-target floor, above the
ladder's own 1.25rem/1.5rem there), 1.875rem at `s`, 2.5rem at `m`, 3rem at `l`, 3.5rem at `xl`.
Also `--lr-known-date-field-height`,
`--lr-known-date-field-gap` (default `--lr-space-s` — gap between the three field blocks),
`--lr-known-date-day-field-width` / `--lr-known-date-month-field-width` (default `--lr-size-3-5em`)
and `--lr-known-date-year-field-width` (default `--lr-size-5em`) — the per-field input widths, not
size-scaled. `--lr-known-date-invalid-border-color` (default `var(--lr-color-danger)`) — border
color of each `field-input` while `:host([data-invalid])` is set. Component-scoped indirection over
the shared `--lr-color-danger` token, so a consumer can retheme just this invalid-field border
without repainting every other component that reads the same shared danger token.

The two height knobs work as a pair on `[part='field-input']`, the same way
`lr-input`/`lr-select`/`lr-combobox`/`lr-date-input` expose theirs:

- `--lr-known-date-field-min-height` is a **floor** whose private default follows each `size` tier
  through the shared ladder; an inherited or direct public value remains authoritative. At the
  small tiers the default exceeds the field's own padding/font-driven height and is what
  actually pins the rendered box — that is how `2xs`/`xs` keep a 24px pointer target; at `l`/`xl`
  the content height already clears it, so it is inert there and only raising it changes anything.
- `--lr-known-date-field-height` pins an **exact** height (both floors and caps), so the three
  inputs can line up with a neighbouring control of a known height. It is **undeclared by
  default** — the field grows to fit its content. Never set it to `auto`: `auto` is a valid
  declared value that wins over the `var()` fallback arm, which would make the per-tier floor
  dead code. To go back to the default behavior, remove the declaration rather than neutralizing
  it. Because the component never declares it, it can be set inline, from an ancestor, or from an
  outer-tree rule.

**Known gotchas:**

- Field _order_ is derived from the locale by formatting a probe date (Jan 2 2026) with
  `Intl.DateTimeFormat` and reading back the part order — not from `Date.parse()`'s mm/dd/yyyy bias.
  It falls back to `month, day, year` only when that sampling fails.
- Auto-advance (typing a field's last digit moves to the next) and backspace-into-the-previous-field
  are this library's own additions, not Web Awesome parity. Auto-advance is purely digit-count
  based, never value based.
- Each `<input>` keeps exactly the digits that were typed — never zero-padded, range-clamped, or
  reverted to a previous value; only the composite `value` is normalized to zero-padded ISO.
- Non-digit characters are stripped in the `input` handler before they reach field state (the
  native `<input>`'s own value is rewritten in the same tick). Locale-specific numerals _are_
  accepted and transliterated to ASCII, not rejected: Arabic-Indic (`٠`–`٩`) and Extended
  Arabic-Indic/Persian (`۰`–`۹`) digits are mapped unconditionally, and the digits of
  `effectiveLocale`'s own numbering system are added on top via `Intl.NumberFormat`, so typing
  `٢٠٢٦` into the year field commits `2026`.
- ArrowLeft/ArrowRight cross fields at a field's text boundary, and the _physical_ key meaning
  "next field" flips under an inherited `dir="rtl"`; the locale-derived field order itself does not.
- A blank composite is `valueMissing` only when **all three** fields are blank; a partially typed
  required date reports `badInput` instead.
- The host carries a `:state(blank)` custom state whenever `value === ''`, and `data-invalid` only
  once touched (first blur out of the whole control) and actually invalid.

**Additional API surface:**

- `click()` — Activates the first native field in locale order.
