@reliability-design/web 1.4.9 → 1.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +161 -2
  2. package/custom-elements.json +18604 -0
  3. package/dist/components/rel-accordion/README.md +9 -2
  4. package/dist/components/rel-alert/README.md +4 -7
  5. package/dist/components/rel-autocomplete/README.md +209 -209
  6. package/dist/components/rel-avatar/README.md +228 -118
  7. package/dist/components/rel-breadcrumb/README.md +1 -0
  8. package/dist/components/rel-button/README.md +9 -9
  9. package/dist/components/rel-calendar/README.md +111 -109
  10. package/dist/components/rel-cardmedia/README.md +1 -1
  11. package/dist/components/rel-carousel/README.md +187 -187
  12. package/dist/components/rel-checkbox/README.md +159 -147
  13. package/dist/components/rel-chip/README.md +141 -138
  14. package/dist/components/rel-container/README.md +1 -1
  15. package/dist/components/rel-date-picker/README.md +221 -206
  16. package/dist/components/rel-drawer/README.md +4 -4
  17. package/dist/components/rel-kbd/README.md +188 -141
  18. package/dist/components/rel-layout/README.md +34 -34
  19. package/dist/components/rel-menu/README.md +17 -10
  20. package/dist/components/rel-menubar/README.md +218 -218
  21. package/dist/components/rel-otp/README.md +4 -8
  22. package/dist/components/rel-pagination/README.md +141 -100
  23. package/dist/components/rel-popover/README.md +220 -203
  24. package/dist/components/rel-radio/README.md +142 -131
  25. package/dist/components/rel-range-picker/README.md +194 -171
  26. package/dist/components/rel-resizable/README.md +220 -199
  27. package/dist/components/rel-segmented-button/README.md +158 -157
  28. package/dist/components/rel-select/README.md +21 -17
  29. package/dist/components/rel-sheet/README.md +3 -2
  30. package/dist/components/rel-signature-pad/README.md +161 -0
  31. package/dist/components/rel-slider/README.md +140 -131
  32. package/dist/components/rel-snackbar/README.md +170 -168
  33. package/dist/components/rel-split-button/README.md +129 -0
  34. package/dist/components/rel-stepper/README.md +171 -171
  35. package/dist/components/rel-switch/README.md +122 -117
  36. package/dist/components/rel-tab/README.md +184 -174
  37. package/dist/components/rel-textfield/README.md +21 -28
  38. package/dist/components/rel-time-picker/README.md +188 -170
  39. package/dist/components/rel-toast/README.md +20 -11
  40. package/dist/components/rel-tooltip/README.md +5 -4
  41. package/dist/components/rel-tree/README.md +157 -0
  42. package/dist/components/rel-upload/README.md +8 -5
  43. package/dist/index.d.mts +1608 -624
  44. package/dist/index.d.ts +1608 -624
  45. package/dist/index.js +11933 -6802
  46. package/dist/index.js.map +1 -1
  47. package/dist/index.mjs +11009 -5885
  48. package/dist/index.mjs.map +1 -1
  49. package/dist/styles/ag-grid.css +274 -0
  50. package/dist/styles/motion.css +31 -0
  51. package/dist/styles/motion.tokens.json +38 -0
  52. package/dist/styles/vars.css +88 -64
  53. package/dist/{vars-DXX6QOAG.css → vars-E22LYFMV.css} +88 -64
  54. package/package.json +61 -37
  55. package/dist/styles/focus-ring.ts +0 -84
package/dist/index.d.mts CHANGED
@@ -1,8 +1,37 @@
1
1
  import * as lit_html from 'lit-html';
2
2
  import * as lit from 'lit';
3
- import { LitElement, PropertyValues, TemplateResult } from 'lit';
3
+ import { LitElement, PropertyValues, PropertyDeclaration, TemplateResult } from 'lit';
4
4
  export * from '@reliability-design/icons';
5
5
 
6
+ type KebabAlias = {
7
+ /** The kebab attribute that is observed in addition to the prop's own. */
8
+ kebab: string;
9
+ /** The reactive property it maps onto. */
10
+ prop: PropertyKey;
11
+ /** The prop's own (joined, legacy) attribute name, which keeps reflecting. */
12
+ legacy: string;
13
+ };
14
+ declare class RelElement extends LitElement {
15
+ /**
16
+ * Explicit kebab spellings the derivation can't reach, as
17
+ * `{ "kebab-name": "propName" }`; e.g. a prop whose own name is lowercase
18
+ * (`supportingtext`). Same behaviour as a derived alias, deprecation
19
+ * notice for the prop's own attribute included.
20
+ */
21
+ static kebabAliases?: Readonly<Record<string, PropertyKey>>;
22
+ /** Props that must not get a derived kebab alias (adds to NO_KEBAB_ALIAS). */
23
+ static noKebabAlias?: readonly PropertyKey[];
24
+ /** The kebab aliases this class observes (for tests, docs and audit tools). */
25
+ static kebabAliasList(): KebabAlias[];
26
+ private static relAliasTables;
27
+ static get observedAttributes(): string[];
28
+ attributeChangedCallback(name: string, old: string | null, value: string | null): void;
29
+ private applyKebabAttribute;
30
+ protected update(changedProperties: PropertyValues): void;
31
+ static createProperty(name: PropertyKey, options?: PropertyDeclaration): void;
32
+ static getPropertyDescriptor(name: PropertyKey, key: string | symbol, options: PropertyDeclaration): PropertyDescriptor | undefined;
33
+ }
34
+
6
35
  /**
7
36
  * Single collapsible section (Figma "Accordion", 6021:1085): a clickable header
8
37
  * that expands/collapses the slotted body with an animated chevron.
@@ -15,7 +44,7 @@ export * from '@reliability-design/icons';
15
44
  * @csspart icon - The trailing icon wrapper
16
45
  * @csspart content - The `role="region"` body that holds the default slot
17
46
  */
18
- declare class RelAccordion extends LitElement {
47
+ declare class RelAccordion extends RelElement {
19
48
  /** Header text. Note it is also the native HTML `title` attribute, so browsers show it as a tooltip on hover. */
20
49
  title: string;
21
50
  /** Whether the body is expanded; toggled by the header and reported via `open-change`. Setting it from code does not fire the event. */
@@ -28,16 +57,12 @@ declare class RelAccordion extends LitElement {
28
57
  size: "md" | "sm" | "large" | "medium" | "small";
29
58
  /**
30
59
  * Header layout (Figma "Type"): `default` = title + trailing rotating chevron; `action` = leading icon slot (rotating chevron by default) + static trailing arrow-expansion icon, with the body indented under the title.
31
- *
32
- * DELIBERATE DIVERGENCE — do not "fix" this to a button. For `Type=Action`
33
- * Figma draws the trailing affordance as a full ghost icon Button instance
34
- * (Size=Medium 40x40, Size=Small 32x32, Style=Ghost, Type=Icon Only); the
35
- * code keeps a bare icon. Ruled by the user: our behaviour stands and the
36
- * DESIGN FILE follows our docs here, not the other way round. The whole
37
- * header is the click target, so a nested button would add a second
38
- * focusable control inside it and two competing hit areas.
39
60
  */
40
61
  type: "default" | "action";
62
+ /**
63
+ * Body treatment (Figma "Content Type"). `text` (default) styles the slotted body as copy: 0 / 16 / 16 / 16px padding, body-md type and the `--rel-text-body` colour. `custom` is for your own layout (a table, a form, a nested card): the body wrapper has no padding, sets no type or colour of its own (the slotted content inherits from the accordion's context), and has an 8px (`--rel-border-radius-sm`) corner radius. With `custom`, `type="action"` does not indent the body. Unknown values fall back to `text`.
64
+ */
65
+ contentType: "text" | "custom";
41
66
  /** Figma "Style" axis — named appearance because `style` collides with the HTML style attribute. `shadow` = 4px-radius card with the shadow-sm effect and no border; `plain` = square corners with a 1px border-subtle stroke and no elevation. */
42
67
  appearance: "shadow" | "plain";
43
68
  /** Figma "State=Disabled": dims the whole component to 40% and blocks toggling by pointer and keyboard. The header keeps its fill and stroke — the dulling is opacity alone, as Figma specifies. */
@@ -45,6 +70,18 @@ declare class RelAccordion extends LitElement {
45
70
  static styles: lit.CSSResult;
46
71
  /** Stable id prefix wiring the header to its panel. */
47
72
  private accordionId;
73
+ /** Body geometry captured just before an open/close re-render: the start point of the animation. */
74
+ private bodyFrom?;
75
+ /** Bumped per toggle so a stale transitionend / timer cannot clear a newer animation's inline styles. */
76
+ private bodyAnimation;
77
+ protected willUpdate(changed: Map<string, unknown>): void;
78
+ protected updated(changed: Map<string, unknown>): void;
79
+ /**
80
+ * Animates max-height between the captured start and the measured end, then
81
+ * hands control back to the stylesheet (open = none, closed = 0). Measuring
82
+ * replaces the old fixed 1000px ceiling, which clipped taller bodies.
83
+ */
84
+ private animateBody;
48
85
  private toggle;
49
86
  render(): lit_html.TemplateResult<1>;
50
87
  }
@@ -64,7 +101,7 @@ type AlertToneInput = AlertTone | AlertToneAlias;
64
101
  * @csspart action - The inline link; rendered only when both `url-text` and `url-href` are set
65
102
  * @csspart close-button - The dismiss (✕) button; rendered only while `dismissible` is true
66
103
  */
67
- declare class RelAlert extends LitElement {
104
+ declare class RelAlert extends RelElement {
68
105
  /**
69
106
  * Semantic tone (Figma "Tone"): `"neutral"` (default) | `"info"` | `"success"` | `"warning"` | `"destructive"`. Drives the fill/border colour, the title colour and the leading icon (info, check-circle, alert-circle, error). `"error"`, `"danger"` and `"critical"` are deprecated aliases of `"destructive"` and log a one-time console notice. Takes precedence over the deprecated `type` prop.
70
107
  */
@@ -84,7 +121,7 @@ declare class RelAlert extends LitElement {
84
121
  urlText?: string;
85
122
  /** `href` of the inline link. The link appears only when both `urlText` and `urlHref` are set. */
86
123
  urlHref?: string;
87
- /** Shows the dismiss (✕) button. Defaults to true; because it is a Lit boolean, `dismissible="false"` is still true — set the property to `false` from script to hide the button. */
124
+ /** Shows the dismiss (✕) button. Defaults to true; `dismissible="false"` (or the property set to `false`) hides the button. */
88
125
  dismissible: boolean;
89
126
  /** True once the alert has been dismissed; hides the host via `:host([dismissed]) { display: none }`. Set it back to `false` to show the alert again. */
90
127
  dismissed: boolean;
@@ -118,7 +155,7 @@ type FieldLabelStyle = "default" | "floating" | "none";
118
155
  * @slot prefix - Custom content at the far left of the control (e.g. a unit dropdown); shares the prefix block with `prefixIcon` / `prefixText`. Hidden when `multiline` is set.
119
156
  * @slot suffix - Custom content at the far right of the control (e.g. an action button); shares the suffix block with `rightIcon` / `suffixText`. Hidden when `multiline` is set.
120
157
  * @csspart label - The visible label element (default and floating label styles)
121
- * @csspart control - The bordered control box. Changed in 1.4.9: in `variant="legend"` this is now the same `div` as every other variant, not the `fieldset` — the fieldset was demoted to a decorative outline layer behind the content so the control sits on the 32/40/48 size ladder and centres its value like the other variants. `::part(control)` still matches; only rules that depended on it being a fieldset do not.
158
+ * @csspart control - The bordered control box. **Changed in 1.4.9:** in `variant="legend"` this is now the same `div` as every other variant, not the `fieldset` — the fieldset was demoted to a decorative outline layer behind the content so the control sits on the 32/40/48 size ladder and centres its value like the other variants. `::part(control)` still matches; only rules that depended on it being a fieldset do not.
122
159
  * @csspart input - The native input / textarea
123
160
  * @csspart prefix - The prefix block (leading icon, prefix text and the `prefix` slot)
124
161
  * @csspart suffix - The suffix block (suffix text, trailing icon and the `suffix` slot)
@@ -128,7 +165,7 @@ type FieldLabelStyle = "default" | "floating" | "none";
128
165
  * @cssprop --rel-field-label-reserve - Height held for the label row whenever no label renders above the control — `labelStyle="none"`, and `labelStyle="floating"` where the label sits inside the control — so the control still lines up with labelled siblings in a form row. Defaults to that size's label line-height plus the wrapper gap (24px at `small`, 26px at `medium` and `large`). Set to `0` to collapse the row entirely (toolbars, table cells).
129
166
  * @cssprop --rel-textfield-surface - Colour of the strip the floating label lays over the control's top border and focus ring so the label appears to sit in a notch. It overshoots the band by 2px on the page side, so it must match the surface the field sits on; defaults to the control's own fill (`--rel-bg-neutral`). Set it on a tinted or elevated surface, otherwise the strip shows as a light dash across the border.
130
167
  */
131
- declare class RelTextfield extends LitElement {
168
+ declare class RelTextfield extends RelElement {
132
169
  /**
133
170
  * Visible label text. Rendered above the control (default), inside it as a
134
171
  * floating label (`labelStyle="floating"`) or as the notch of the `legend`
@@ -271,11 +308,19 @@ type CheckboxDensity = "compact" | "comfortable";
271
308
  type CheckboxAlignment = "leading" | "trailing";
272
309
  /**
273
310
  * Tri-state checkbox (Figma "Checkbox"): a hidden native checkbox behind a custom box, with label, optional prefix icon and supporting text.
311
+ * Form-associated, so an outer `<label>` (wrapping it, or `for=`) toggles it and names it; it still sets no form value and is not submitted.
274
312
  * @csspart base - The outer wrapper that carries the `state` / `status` classes
275
313
  * @csspart label - The visible label text
276
314
  * @csspart helper - The supporting text (rendered beside the box in comfortable density, under the row in compact)
277
315
  */
278
- declare class RelCheckbox extends LitElement {
316
+ declare class RelCheckbox extends RelElement {
317
+ /** Form-associated only so an outer `<label>` has a control (see utils/outer-label.ts). No form value is set. */
318
+ static formAssociated: boolean;
319
+ /** `supporting-text` is the canonical attribute; the prop's own lowercase
320
+ * name means RelElement can't derive it, so it is declared here. */
321
+ static kebabAliases: {
322
+ "supporting-text": string;
323
+ };
279
324
  /** Label text rendered next to the box. */
280
325
  label: string;
281
326
  /** rel-icon name rendered between the checkbox and the label (label prefix icon); scales with `size`. Unset renders no icon and reserves no space. */
@@ -284,6 +329,8 @@ declare class RelCheckbox extends LitElement {
284
329
  supportingtext: string;
285
330
  /** Visual and interaction state (Figma State): `"error"` swaps in the danger colours, `"disabled"` blocks input and dims the whole control to 0.4 opacity (colours unchanged, as Figma specifies). */
286
331
  state: CheckboxState;
332
+ /** Disables the checkbox exactly as `state="disabled"` does (`state` stays the documented spelling). Either one disables it, and so does an ancestor `<fieldset disabled>`; none of them changes the others. New in 1.5.0. */
333
+ disabled: boolean;
287
334
  /** Check state. `"indeterminate"` is programmatic-only — a user toggle always resolves to checked or unchecked. */
288
335
  status: CheckboxStatus;
289
336
  /** Value reported in the `rel-change` event detail (it is not submitted with a form). */
@@ -309,18 +356,40 @@ declare class RelCheckbox extends LitElement {
309
356
  * absent or not a usable name on its own — a checkbox in a table row, or one
310
357
  * whose meaning comes from its column header.
311
358
  *
312
- * Normally you do not need it: the input is wrapped in the `<label>` that
359
+ * Normally unnecessary: the input is wrapped in the `<label>` that
313
360
  * renders `label`, so the visible text already names it natively. Setting
314
361
  * this puts an `aria-label` on the input, which OVERRIDES that wrapping
315
362
  * label — so set it only when the visible text is the wrong name, not to
316
- * repeat it. Left unset the attribute is not emitted at all and the native
317
- * labelling is untouched. */
363
+ * repeat it. Left unset the attribute is not emitted at all.
364
+ *
365
+ * An outer `<label>` (wrapping the checkbox, or `for=`) names it only when
366
+ * neither this nor a visible `label` is set. */
318
367
  fieldAriaLabel?: string;
368
+ /**
369
+ * camelCase alias of `supportingtext`, matching rel-textfield and rel-select.
370
+ * The declared property is lowercase, so `el.supportingText = "…"`, lit
371
+ * `.supportingText=${…}` and Angular `[supportingText]` used to set an unused
372
+ * expando and render nothing. Reading or writing either name now hits the same
373
+ * reactive property; the attribute stays `supportingtext`.
374
+ */
375
+ get supportingText(): string;
376
+ set supportingText(val: string);
319
377
  /** Boolean view of `status`: `true` when `status === "checked"`; setting it writes `"checked"` / `"unchecked"`. */
320
378
  get checked(): boolean;
321
379
  set checked(val: boolean);
322
380
  private _onChange;
323
381
  private _onTouched;
382
+ private readonly internals;
383
+ /** A click on an outer label arrives at the host; route it through the
384
+ * native input so it runs the same `toggle()` a direct click does. */
385
+ private readonly outerLabel;
386
+ /** `state="disabled"`, the `disabled` property, or an ancestor `<fieldset disabled>`. */
387
+ private get isDisabled();
388
+ /** @internal Form-associated lifecycle: an ancestor `<fieldset disabled>` disables the control too (utils/outer-label.ts, 3). */
389
+ formDisabledCallback(): void;
390
+ /** fieldAriaLabel wins; then the visible label (native, via the inner
391
+ * <label>, so no attribute); then an outer <label>'s text. */
392
+ private get inputAriaLabel();
324
393
  static styles: lit.CSSResult;
325
394
  protected willUpdate(changed: PropertyValues<this>): void;
326
395
  /** Trailing has no compact layout in the spec — trailing wins. */
@@ -335,6 +404,8 @@ declare class RelCheckbox extends LitElement {
335
404
  render(): lit_html.TemplateResult<1>;
336
405
  }
337
406
 
407
+ /** Status badge on the avatar's bottom-right edge (Figma ".Avatar Badge" Type). `""` = none. */
408
+ type AvatarBadge = "" | "dot" | "icon";
338
409
  /** Diameter step, spelled out. */
339
410
  type AvatarSize = "large" | "medium" | "small" | "extra-small";
340
411
  /** Alternative spellings, not deprecations — the short names keep working. */
@@ -342,8 +413,11 @@ type AvatarSizeAlias = "lg" | "md" | "sm" | "xs";
342
413
  type AvatarSizeInput = AvatarSize | AvatarSizeAlias;
343
414
  /**
344
415
  * Circular avatar (Figma "Avatar"): shows a photo, text initials or a slotted icon in one of four sizes and three fill variants.
345
- * @slot - Icon content rendered when `type="icon"` (typically an inline SVG, sized 20 / 18 / 16 / 12px by `size`)
416
+ * @slot - Icon content rendered when `type="icon"` (typically an inline SVG, sized 20 / 18 / 16 / 12px by `size`); not rendered when the `icon` attribute is set
346
417
  * @csspart base - The circular wrapper that carries the size, variant and inline `bg` / `color` overrides
418
+ * @csspart badge - The status badge on the bottom-right edge, rendered when `badge` is set (new in 1.5.0)
419
+ * @cssprop --rel-avatar-ring-color - Colour of the `ring`; defaults to `--rel-border-inverse` (new in 1.5.0)
420
+ * @cssprop --rel-avatar-badge-bg - Fill of the `badge`; defaults to `--rel-bg-success` for `dot` and `--rel-bg-neutral-elevated-2` for `icon` (new in 1.5.0)
347
421
  * @csspart icon - The `<img>` (`type="image"`) or the icon wrapper span around the slot (`type="icon"`)
348
422
  * @csspart label - The `<span>` holding the initials when `type="text"`
349
423
  * @cssprop --rel-avatar-bg-fill - Background colour of the `fill` variant
@@ -357,7 +431,7 @@ type AvatarSizeInput = AvatarSize | AvatarSizeAlias;
357
431
  * @cssprop --rel-avatar-size - Overrides the circle's width/height for sizes between (or below) the `size` presets; defaults to the preset's diameter
358
432
  * @cssprop --rel-avatar-font-size - Overrides the initials' font-size, for use alongside `--rel-avatar-size`; defaults to the preset's text role
359
433
  */
360
- declare class RelAvatar extends LitElement {
434
+ declare class RelAvatar extends RelElement {
361
435
  /** Diameter step: `large` 40px, `medium` 32px, `small` 24px, `extra-small` 20px; each binds the matching body-*-semibold text role for initials. `lg` / `md` / `sm` / `xs` are alternative spellings and stay supported — they are normalised to the full words, not deprecated. */
362
436
  size: AvatarSizeInput;
363
437
  /** What is rendered inside the circle: `image` shows `src`, `text` shows the `text` initials, `icon` shows the default slot. */
@@ -374,6 +448,16 @@ declare class RelAvatar extends LitElement {
374
448
  bg: string;
375
449
  /** Any CSS colour applied inline as `color` on the circle (initials / icon), overriding the variant text colour. */
376
450
  color: string;
451
+ /** New in 1.5.0. rel-icon name shown for `type="icon"` (Figma's "Icon" swap, e.g. `person`), as an alternative to slotting an SVG. When set it replaces the slot. */
452
+ icon: string;
453
+ /** New in 1.5.0. Figma "Show Ring": a 2px ring in `--rel-border-inverse` just outside the circle, which separates overlapping avatars. It does not change the avatar's size. */
454
+ ring: boolean;
455
+ /** New in 1.5.0. Figma "Show Badge": a status badge on the bottom-right edge with a 1px `--rel-border-inverse` edge. `dot` is a `--rel-bg-success` disc; `icon` is a `--rel-bg-neutral-elevated-2` disc holding `badge-icon`. 12 / 10 / 8 / 6px for large / medium / small / extra-small. Empty (the default) renders none. */
456
+ badge: AvatarBadge;
457
+ /** New in 1.5.0. rel-icon name inside a `badge="icon"` badge. */
458
+ badgeIcon: string;
459
+ /** New in 1.5.0. What the badge means, for screen readers (e.g. "Online"). Without it the badge is decorative and hidden from assistive technology, because a dot conveys its meaning by colour alone. */
460
+ badgeLabel: string;
377
461
  static styles: lit.CSSResult;
378
462
  /** Alternative spellings, NOT deprecations — no console notice. Normalising
379
463
  * the property rather than duplicating every selector keeps the reflected
@@ -381,6 +465,7 @@ declare class RelAvatar extends LitElement {
381
465
  * the CSS. Same shape as rel-chip and rel-slider. */
382
466
  protected willUpdate(changed: Map<string, unknown>): void;
383
467
  render(): lit_html.TemplateResult<1>;
468
+ private renderBadge;
384
469
  }
385
470
 
386
471
  /** Canonical semantic tones (Figma "Tone"): neutral | destructive | warning | success | info. */
@@ -406,12 +491,15 @@ type ChipBorder = "default" | "rounded";
406
491
  * @csspart base - The chip body (`.chip`) that carries size, tone, variant and border styling
407
492
  * @csspart icon - The leading `<rel-icon>` (rendered only when `leading-icon` is set and no avatar)
408
493
  * @csspart label - The label text
494
+ * @csspart remove - The remove `<button>` (rendered only when `removable`)
409
495
  * @cssprop --rel-chip-focus-ring - Colour of the keyboard focus outline; default `var(--rel-border-focus)`
410
496
  */
411
- declare class RelChip extends LitElement {
497
+ declare class RelChip extends RelElement {
412
498
  static shadowRootOptions: ShadowRootInit;
413
499
  /** Text shown in the chip; also its accessible name and the drag payload. */
414
500
  label: string;
501
+ /** Accessible name for an icon-only or intentionally unlabelled chip; overrides `label` for the chip and its remove button. */
502
+ fieldAriaLabel?: string;
415
503
  /** Colour tone (Figma Tone): `neutral`, `destructive`, `warning`, `success` or `info`. Combined with `variant`. `"danger"` is a deprecated alias of `"destructive"`: it keeps working, logs a one-time console notice, and is normalised to `"destructive"` on the property and the reflected attribute. */
416
504
  tone: ChipToneInput;
417
505
  /** Emphasis (Figma Style): `fill` = solid tone background, `subtle` = tinted background with tone-coloured text. Figma's own spellings `primary` and `secondary` are accepted and normalised to `fill` / `subtle`. */
@@ -428,6 +516,8 @@ declare class RelChip extends LitElement {
428
516
  avatar?: string;
429
517
  /** Shows a remove (close) button after the label; clicking it sets `removed` and fires `removed`. */
430
518
  removable: boolean;
519
+ /** New in 1.5.0. Makes the chip body plain text: no `button` role, no Tab stop, no Enter / Space / Delete handling and no host click re-dispatch. The remove button (when `removable`) stays a real button. For chips that only display a value inside another control, e.g. rel-select's multi-select chips. */
520
+ presentational: boolean;
431
521
  /** `true` once the chip has been removed — the host is hidden via `:host([removed])`. Reflected so CSS and Angular can see it; reset to `false` to show the chip again. */
432
522
  removed: boolean;
433
523
  /** Opt-in native drag-and-drop: sets `draggable="true"` on the host, shows the Figma "Dragged" state during a real drag gesture, and makes Left/Right arrow keys fire `chip-move`. */
@@ -459,7 +549,7 @@ declare global {
459
549
  * An item is a plain string or any object read through displayField/valueField.
460
550
  * Two keys are read off objects directly, whatever those fields are named:
461
551
  * `icon` (a `rel-icon` name shown before the label) and `disabled` (`true`
462
- * renders the row dulled and makes it unselectable by click or keyboard).
552
+ * renders the row's text in `--rel-text-disabled`, icon unchanged, and makes it unselectable by click or keyboard).
463
553
  */
464
554
  type AutocompleteItem = string | Record<string, unknown>;
465
555
  type AutocompleteFilter = "contains" | "starts-with" | "none";
@@ -498,9 +588,9 @@ type AutocompleteSearchFn = (query: string) => Promise<AutocompleteItem[]>;
498
588
  * @cssprop --rel-autocomplete-icon-color - Colour of a row's leading `icon`
499
589
  * @cssprop --rel-autocomplete-status-color - Colour of the loading / no-results status rows
500
590
  * @cssprop --rel-autocomplete-menu-max-height - Scroll ceiling of the suggestion panel; default `360px`. Code-owned rather than Figma-derived — every Figma Menu variant hugs its content with no ceiling, which is not shippable for an unbounded result list.
501
- * @cssprop --rel-overlay-fallback-z - z-index of the panel on the NO-POPOVER-API fallback path only. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with rel-select, rel-autocomplete and the other pickers so the whole overlay family can be moved from one place; default 1001.
591
+ * @cssprop --rel-overlay-fallback-z - z-index of the panel on the **no-Popover-API fallback path only**. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with the other overlays (select, autocomplete, the three pickers) so the whole family can be moved from one place.
502
592
  */
503
- declare class RelAutocomplete extends LitElement {
593
+ declare class RelAutocomplete extends RelElement {
504
594
  /** Local (or fallback initial) suggestion list. Ignored while searchFn drives results. Object items are read through `displayField` / `valueField` plus the fixed keys `icon`, `disabled` and `supportingText` (the last renamable via `supporting-field`); `supportingText` renders as a second line in the row and is matched by the filter alongside the label. */
505
595
  items: AutocompleteItem[];
506
596
  /** Committed value — the selected item's valueField string ("" = none). */
@@ -529,7 +619,7 @@ declare class RelAutocomplete extends LitElement {
529
619
  * THE TEXT INPUT STAYS A SEARCH BOX. In single mode the input shows the
530
620
  * selected label, which is the point of a type-ahead; in multi mode there is
531
621
  * no single label to show, and writing a value list into the field would
532
- * destroy the query the user is typing. `rel-select` can put chips inside its
622
+ * destroy the query the user is typing. `rel-select` can lay chips over its
533
623
  * trigger because a trigger is not an editable surface — an input is. So the
534
624
  * chips render beneath the field, the query clears after each toggle so the
535
625
  * next search starts fresh, and the panel stays open.
@@ -614,7 +704,7 @@ declare class RelAutocomplete extends LitElement {
614
704
  private addGlobalListeners;
615
705
  private removeGlobalListeners;
616
706
  private onOutsidePointerDown;
617
- private onPageScroll;
707
+ private scrollWatch;
618
708
  private openPanel;
619
709
  private closePanel;
620
710
  private showNativePopover;
@@ -672,6 +762,38 @@ declare global {
672
762
  }
673
763
  }
674
764
 
765
+ /**
766
+ * New in 1.5.0. A row of overlapping avatars (Figma "Avatar Group", 9243:85), with an optional "+N" circle for the people not shown. Slot `rel-avatar` elements into it: the group gives each one its `size` and a `ring`, overlaps them by a quarter of the diameter, and hides those beyond `max`.
767
+ * @slot - The `rel-avatar` elements, in display order
768
+ * @csspart base - The `role="group"` row holding the avatars and the overflow circle
769
+ * @csspart overflow - The "+N" circle (a `rel-avatar` of `type="text"`, `variant="fill"`) shown when people are not displayed
770
+ * @cssprop --rel-avatar-group-overlap - How far each avatar overlaps the previous one (a positive length); defaults to Figma's 10 / 8 / 6 / 4px for large / medium / small / extra-small
771
+ */
772
+ declare class RelAvatarGroup extends RelElement {
773
+ /** Diameter step given to every avatar in the group and to the "+N" circle: `large` 40px, `medium` 32px, `small` 24px, `extra-small` 20px. `lg` / `md` / `sm` / `xs` are accepted and normalised to the full words. */
774
+ size: AvatarSizeInput;
775
+ /** Most avatars to show; the rest are hidden and counted in the "+N" circle. `0` (the default) shows them all. */
776
+ max: number;
777
+ /** The full number of people when only some avatars are slotted (e.g. 3 slotted of 57). The "+N" circle then counts everyone not shown. `0` (the default) counts the slotted avatars only. */
778
+ total: number;
779
+ /** Accessible name of the group (e.g. "Project members"). No default: without it the group has no name. */
780
+ label: string;
781
+ /** Accessible name of the "+N" circle; `{n}` is replaced by the count. */
782
+ overflowLabel: string;
783
+ private avatarCount;
784
+ static styles: lit.CSSResult;
785
+ private get avatars();
786
+ private get visibleCount();
787
+ /** People counted in the "+N" circle. */
788
+ get overflowCount(): number;
789
+ protected willUpdate(changed: PropertyValues<this>): void;
790
+ protected updated(changed: PropertyValues<this>): void;
791
+ /** Give every slotted avatar the group's size and a ring, and hide the ones beyond `max`. */
792
+ private syncAvatars;
793
+ private onSlotChange;
794
+ render(): lit_html.TemplateResult<1>;
795
+ }
796
+
675
797
  /** Canonical size steps — full words, as elsewhere in the library. */
676
798
  type BadgeSize = "small" | "medium" | "large" | "extra-large";
677
799
  /** Short size spellings, accepted as alternatives. */
@@ -684,7 +806,7 @@ type BadgeSizeInput = BadgeSize | BadgeSizeAlias;
684
806
  * @cssprop --rel-badge-color - Text colour of the count; the `text-color` prop writes this on the host
685
807
  * @cssprop --rel-badge-dot-size - Diameter of the `dot` variant; default 6px, the single dot size Figma draws. Set it to restore the larger discs shipped previously.
686
808
  */
687
- declare class RelBadge extends LitElement {
809
+ declare class RelBadge extends RelElement {
688
810
  /** Text shown in the `number` variant (a count such as "5" or "99+"); not rendered by `variant="dot"` but still used to build the accessible name. */
689
811
  number: string;
690
812
  /** Explicit accessible name; when empty it is composed as "<number> <suffix>" or, with no number, "New <suffix>". Attribute is `arialabel`. */
@@ -726,9 +848,11 @@ interface BreadcrumbItem {
726
848
  * @csspart base - The `<nav>` landmark wrapping the ordered list.
727
849
  * @csspart label - The non-link current-page text (`aria-current="page"`), also used for any item without a `url`.
728
850
  */
729
- declare class RelBreadcrumbs extends LitElement {
851
+ declare class RelBreadcrumbs extends RelElement {
730
852
  /** Ordered trail, root first: `{ label: string; url?: string }[]`. Items with a `url` render as links except the last one, which is always the current page (and never a link). Set as a property — arrays do not survive attribute serialisation. */
731
853
  items: BreadcrumbItem[];
854
+ /** Name the trail when several breadcrumb landmarks share a page. */
855
+ ariaLabel: string;
732
856
  static styles: lit.CSSResult;
733
857
  render(): lit_html.TemplateResult<1>;
734
858
  }
@@ -757,7 +881,7 @@ type ButtonTone = "default" | "primary" | "secondary" | "critical" | "success" |
757
881
  * @csspart indicator - The loading spinner, rendered only while `loading` is set.
758
882
  * @cssprop --rel-button-min-width - Overrides the per-size minimum width (Figma small/medium/large = 80/100/120px, ghost small = 56px). Ignored by `icon-only` and `link`.
759
883
  */
760
- declare class RelButton extends LitElement {
884
+ declare class RelButton extends RelElement {
761
885
  /** Style axis (Figma "Style"). Canonical: `default` (legacy neutral fill), `primary`, `secondary` (outlined), `ghost` (text-only), `link` (underlined inline text), `static` (transparent, static-light label for dark/colored surfaces). Deprecated aliases still accepted: `outline` = secondary, `text` = ghost, and `icon` (pre-2.0 40px circular icon button — use `icon-only` with a style instead). */
762
886
  variant: ButtonVariant;
763
887
  /**
@@ -805,9 +929,10 @@ declare class RelButton extends LitElement {
805
929
  * @csspart action - The previous-month and next-month buttons
806
930
  * @csspart label - The month/year heading and each weekday column header
807
931
  * @csspart list - The weekday-header row and the day grid
808
- * @csspart item - Each day cell button
932
+ * @csspart item - Each day cell button (unavailable days also carry `unavailable`)
933
+ * @csspart unavailable - Day cells that cannot be picked (outside `min` / `max`, or blocked by `isDateDisabled`), alongside `item`. They are `aria-disabled` and stay focusable, so `::part(item):disabled` does not match them; style them with `::part(unavailable)`
809
934
  */
810
- declare class RelCalendar extends LitElement {
935
+ declare class RelCalendar extends RelElement {
811
936
  /** Selected date as an ISO `YYYY-MM-DD` string, or `null` for no selection; setting it also moves the visible month. */
812
937
  value: string | null;
813
938
  /** Earliest selectable date (ISO `YYYY-MM-DD`); earlier days render disabled. */
@@ -841,13 +966,57 @@ declare class RelCalendar extends LitElement {
841
966
  private announceMonth;
842
967
  private previousMonth;
843
968
  private nextMonth;
969
+ /**
970
+ * A click is an interaction with that day even when the day cannot be
971
+ * picked, so it takes the Tab stop first. Set explicitly rather than left to
972
+ * handleDayGridFocusin because Safari does not focus a button on click.
973
+ */
974
+ private handleDayClick;
844
975
  private selectDay;
845
- private handleKeyDown;
976
+ private dayButtonHasFocus;
977
+ /**
978
+ * Day-grid keys. Movement is by DATE, never by grid index (an index
979
+ * addresses a different date once the view flips month). The key map is
980
+ * period-page's dayGridTarget, shared with rel-date-picker and
981
+ * rel-range-picker: arrows ±1/±7 days, Home/End the week (honouring
982
+ * `week-start`), PageUp/PageDown a month, Shift+PageUp/PageDown a year,
983
+ * clamped to the target month's last day.
984
+ */
985
+ private handleDayKeydown;
846
986
  /**
847
- * Index of the single grid cell carrying tabindex="0". The old expression
987
+ * Move the rove AND focus to `date`, flipping the view when the date has left
988
+ * the visible month. Same shape as the pickers' moveGridFocusTo, and the
989
+ * order matters: `focusedDate` is set from the date BEFORE focus moves,
990
+ * because lit reuses the day buttons across the month re-render, so the
991
+ * focused element may not change and no focus event would fire to record it.
992
+ */
993
+ private moveGridFocusTo;
994
+ /**
995
+ * Focus arriving any other way — a click, Tab / Shift+Tab, or a programmatic
996
+ * focus() — moves the Tab stop to that day too, so Tab out of the grid and
997
+ * back returns to the day last interacted with. This catches every case in
998
+ * which the focused ELEMENT changes; the one case where it does not (a month
999
+ * flip that reuses the focused button) is covered by moveGridFocusTo setting
1000
+ * the date first.
1001
+ */
1002
+ private handleDayGridFocusin;
1003
+ /**
1004
+ * Index of the single grid cell carrying tabindex="0": the day last
1005
+ * interacted with (`focusedDate`), else the selection, else today, else the
1006
+ * first enabled day of the month, else the first cell. The old expression
848
1007
  * (`isSelected || (index === 0 && !focusedDate)`) left the grid with ZERO
849
- * tab stops once the user arrowed around without a selection — Tab could
850
- * never re-enter the calendar.
1008
+ * tab stops once the user arrowed around without a selection; the trailing
1009
+ * fallback keeps it at exactly one by construction.
1010
+ *
1011
+ * `focusedDate` is written by moveGridFocusTo (arrows / Home / End / Page
1012
+ * keys, from the date, before focus moves), handleDayClick and
1013
+ * handleDayGridFocusin (Tab or any other focus route), the same model as
1014
+ * rel-date-picker and rel-range-picker. It counts wherever it sits in the
1015
+ * 42-cell grid, out-of-month cells included: every date appears at most
1016
+ * once, so this cannot create a second Tab stop, and after a chevron the
1017
+ * day last used stays the Tab stop while it is still on screen. Unavailable
1018
+ * days are aria-disabled, not native disabled, so a Tab stop on one is still
1019
+ * reachable.
851
1020
  */
852
1021
  private getRovingIndex;
853
1022
  render(): lit_html.TemplateResult<1>;
@@ -862,7 +1031,7 @@ declare class RelCalendar extends LitElement {
862
1031
  * @csspart header - The media region wrapper (present only when `type="media"`)
863
1032
  * @csspart content - The padded body that wraps the default slot
864
1033
  */
865
- declare class RelCard extends LitElement {
1034
+ declare class RelCard extends RelElement {
866
1035
  /**
867
1036
  * Figma "Type" axis: custom (default) renders slotted content with card
868
1037
  * padding; media adds a flush media region (slot name="media") above the
@@ -880,17 +1049,17 @@ declare class RelCard extends LitElement {
880
1049
  * @csspart track - The flex row / column that translates to reveal the current slide.
881
1050
  * @csspart ${direction}-button - The two arrow buttons, exposed as `prev-button` and `next-button` (`type="side-arrows"` overlays them on the viewport; `type="bottom-arrows"` inlines them in the dots row).
882
1051
  * @csspart dots - The indicator row under the viewport (also hosts the arrows for `type="bottom-arrows"`).
883
- * @csspart dot - One indicator button per reachable position (per slide at `panels="1"`); the active one carries the `active` class and `aria-current="true"`.
1052
+ * @csspart dot - One indicator button per reachable position (per slide at `panels="1"`); the active one carries the `active` class and `aria-current="true"`. Since 1.5.0 the button is a transparent target cell of at least 24 × 24px and the visible dot is its `::before` (style it with `::part(dot)::before`).
884
1053
  * @csspart dot-group - The row of dots inside `dots` (for `type="bottom-arrows"` it is the middle child, between the two arrows).
885
1054
  * @cssprop --rel-carousel-height - Viewport height in `orientation="vertical"` (default 320px); the horizontal viewport takes the height of its tallest slide.
886
1055
  * @cssprop --rel-carousel-nav-size - Width / height of the arrow buttons (default 32px, matching Figma; applies to both side and bottom arrows).
887
- * @cssprop --rel-carousel-dot-size - Diameter of an inactive dot (default 8px).
888
- * @cssprop --rel-carousel-dot-size-active - Width of the active (brand-coloured, pill-shaped) dot (default 16px).
1056
+ * @cssprop --rel-carousel-dot-size - Diameter of an inactive dot (default 8px); its target cell is at least 24px.
1057
+ * @cssprop --rel-carousel-dot-size-active - Width of the active (brand-coloured, pill-shaped) dot (default 16px); its target cell is at least 24px.
889
1058
  * @cssprop --rel-carousel-slide-width - Written by the component on its own host to the computed panel width (viewport width for `panels="1"`, a fraction of it above that); not meant to be set by consumers (an inline value is overwritten on resize).
890
- * @cssprop --rel-carousel-slide-display - `display` given to each slide in `orientation="vertical"` only; defaults to `flex`, which is what centres the slide's content in the fixed-height viewport as Figma draws it. Set it to `block` to opt out if forcing flex reflows your slide's own children. Horizontal slides are never touched.
1059
+ * @cssprop --rel-carousel-slide-display - `display` given to each slide in `orientation="vertical"` only, which is what centres the slide's content in the fixed-height viewport as Figma draws it. Set it to `block` to opt out if forcing flex reflows your slide's own children. Horizontal slides are never touched.
891
1060
  * @cssprop --rel-carousel-slide-height - Written by the component on its own host to the computed panel height (used for vertical slides); not meant to be set by consumers.
892
1061
  */
893
- declare class RelCarousel extends LitElement {
1062
+ declare class RelCarousel extends RelElement {
894
1063
  /** Index of the current slide (0-based). Set it to jump; it is clamped to the slide count and reflected as an attribute. */
895
1064
  value: number;
896
1065
  /** Wrap from the last slide to the first (and back) with a seamless clone transition instead of stopping at the ends. */
@@ -899,7 +1068,7 @@ declare class RelCarousel extends LitElement {
899
1068
  autoplay: boolean;
900
1069
  /** Autoplay delay between slides, in milliseconds (default 5000). */
901
1070
  interval: number;
902
- /** Pause autoplay while the pointer is over the carousel or while dragging (default true; switch off via the property, e.g. `el.pauseOnHover = false`). */
1071
+ /** Pause autoplay while the pointer is over the carousel or while dragging (default true; switch off with `pause-on-hover="false"` or `el.pauseOnHover = false`). */
903
1072
  pauseOnHover: boolean;
904
1073
  /** Legacy toggle for the overlaid previous / next arrows; ignored whenever `type` is set. */
905
1074
  showButtons: boolean;
@@ -1006,7 +1175,7 @@ declare class RelCarousel extends LitElement {
1006
1175
  * @csspart icon - The image wrapper above the content, rendered only when `image-src` is set.
1007
1176
  * @csspart content - The padded area holding the title, subtitle and slotted body.
1008
1177
  */
1009
- declare class RelCardMedia extends LitElement {
1178
+ declare class RelCardMedia extends RelElement {
1010
1179
  /** URL of the hero image shown full-width at the top of the card. Empty (the default) omits the image section entirely. */
1011
1180
  imageSrc: string;
1012
1181
  /** Alternative text for the hero image. Replace the generic default with a real description, or an empty string for purely decorative images. */
@@ -1029,16 +1198,17 @@ declare class RelCardMedia extends LitElement {
1029
1198
  * @csspart trigger - The field box that wraps the text input and the clear/calendar icons
1030
1199
  * @csspart value - The editable text input inside the trigger
1031
1200
  * @csspart shortcuts - The Today / Tomorrow / Yesterday chip row at the top of the popover
1032
- * @cssprop --rel-overlay-fallback-z - z-index of the panel on the NO-POPOVER-API fallback path only. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with rel-select, rel-autocomplete and the other pickers so the whole overlay family can be moved from one place; default 1001.
1201
+ * @csspart unavailable - Day cells that cannot be picked (outside `min-date` / `max-date`, or blocked by `isDateDisabled`). They are `aria-disabled` and stay focusable, so `:disabled` does not match them; style them with `::part(unavailable)`
1202
+ * @cssprop --rel-overlay-fallback-z - z-index of the panel on the **no-Popover-API fallback path only**. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with the other overlays (select, autocomplete, the three pickers) so the whole family can be moved from one place.
1033
1203
  */
1034
- declare class RelDatePicker extends LitElement {
1204
+ declare class RelDatePicker extends RelElement {
1035
1205
  /** Selected date as an ISO `YYYY-MM-DD` string, or `null` when empty; setting it also moves the popover to that month. */
1036
1206
  value: string | null;
1037
1207
  /** Placeholder text shown while the field is empty (visual only; the accessible name comes from `label`, or "Select date" when unset). */
1038
1208
  placeholder: string;
1039
1209
  /** Visible label rendered above the field and wired to the input via `aria-labelledby`; `null` renders no label. */
1040
1210
  label: string | null;
1041
- /** Disables the field, the icons and the popover; the host dims to 40% opacity. */
1211
+ /** Disables the field, the icons and the popover. The field looks exactly like a disabled `rel-textfield`: the whole field at 0.65 opacity, an elevated-2 box with the accent border, the label in `--rel-text-label` and the value and placeholder in `--rel-text-caption` (changed in 1.5.0: they were `--rel-text-disabled` under the 0.65, which left them barely readable). */
1042
1212
  disabled: boolean;
1043
1213
  /** Appends a `*` to the label and sets `aria-required`; no validation is performed. */
1044
1214
  required: boolean;
@@ -1073,8 +1243,8 @@ declare class RelDatePicker extends LitElement {
1073
1243
  /**
1074
1244
  * Figma ".Date Picker Calendar" Show Shortcuts: renders the Today / Tomorrow /
1075
1245
  * Yesterday chip row and its divider at the top of the popover. On by default —
1076
- * write `show-shortcuts="false"` to hide both (a plain boolean attribute could
1077
- * not express a default of `true`). The typed `T` / `T±N` shortcuts and the
1246
+ * write `show-shortcuts="false"` to hide both (a plain boolean attribute could not
1247
+ * express a default of `true`). The typed `T` / `T±N` shortcuts and the
1078
1248
  * in-panel `T` key keep working either way.
1079
1249
  */
1080
1250
  showShortcuts: boolean;
@@ -1090,7 +1260,7 @@ declare class RelDatePicker extends LitElement {
1090
1260
  size: "small" | "medium" | "large";
1091
1261
  /** Visual style of the field; `outlined` is the only variant today. */
1092
1262
  variant: "outlined";
1093
- /** Stretches the host to fill its parent's width (attribute `fullwidth`); off by default so the field keeps its intrinsic inline width. */
1263
+ /** Stretches the host to fill its parent's width (attribute `full-width`); off by default so the field keeps its intrinsic inline width. */
1094
1264
  fullWidth: boolean;
1095
1265
  /** Custom predicate that disables individual days (return `true` to disable); applied on top of `minDate`/`maxDate` and also rejects typed input. Property only — assign it from JavaScript. */
1096
1266
  isDateDisabled?: (date: Date) => boolean;
@@ -1126,13 +1296,14 @@ declare class RelDatePicker extends LitElement {
1126
1296
  disconnectedCallback(): void;
1127
1297
  private handleClickOutside;
1128
1298
  private handleEscapeKey;
1299
+ protected willUpdate(changedProperties: Map<string, any>): void;
1129
1300
  updated(changedProperties: Map<string, any>): void;
1130
1301
  private showTopLayer;
1131
1302
  private hideTopLayer;
1132
1303
  private onReposition;
1133
- private onPageScroll;
1304
+ private scrollWatch;
1134
1305
  private updatePopoverPosition;
1135
- /** Focus the roving day cell (selected day, else today, else first). */
1306
+ /** Focus the roving day cell (the Tab stop — see getRovingIndex). */
1136
1307
  private focusPanelSelection;
1137
1308
  private parseISODate;
1138
1309
  private formatISODate;
@@ -1160,6 +1331,12 @@ declare class RelDatePicker extends LitElement {
1160
1331
  private closeCalendarPopover;
1161
1332
  private previousMonth;
1162
1333
  private nextMonth;
1334
+ /**
1335
+ * A click is an interaction with that day even when the day cannot be
1336
+ * picked, so it takes the Tab stop first. Set explicitly rather than left to
1337
+ * handleDayGridFocusin because Safari does not focus a button on click.
1338
+ */
1339
+ private handleDayClick;
1163
1340
  private selectDay;
1164
1341
  /** Commit a date: set value, reset typed text/error, fire rel-change. */
1165
1342
  private commitDate;
@@ -1184,7 +1361,29 @@ declare class RelDatePicker extends LitElement {
1184
1361
  private handleInputBlur;
1185
1362
  /** T inside the panel (grid/header focus) applies Today directly. */
1186
1363
  private handlePanelKeydown;
1364
+ /**
1365
+ * Day-grid keys. Movement is by DATE (the key map is shared with
1366
+ * rel-range-picker via period-page's dayGridTarget), never by grid index:
1367
+ * an index addresses a different date once the view flips month.
1368
+ */
1187
1369
  private handleDayKeydown;
1370
+ /**
1371
+ * Move the rove AND focus to `date`, flipping the view when the date has left
1372
+ * the visible month. Same shape as rel-range-picker.moveGridFocusTo, and the
1373
+ * order matters: `focusedDate` is set from the date BEFORE focus moves,
1374
+ * because lit reuses the day buttons across the month re-render, so the
1375
+ * focused element may not change and no focus event would fire to record it.
1376
+ */
1377
+ private moveGridFocusTo;
1378
+ /**
1379
+ * Focus arriving any other way — a click, Tab / Shift+Tab, or a programmatic
1380
+ * focus() — moves the Tab stop to that day too, so Tab out of the grid and
1381
+ * back returns to the day last interacted with. This catches every case in
1382
+ * which the focused ELEMENT changes; the one case where it does not (a month
1383
+ * flip that reuses the focused button) is covered by moveGridFocusTo setting
1384
+ * the date first.
1385
+ */
1386
+ private handleDayGridFocusin;
1188
1387
  /**
1189
1388
  * The chevrons step by whatever the visible body is made of, so they stay
1190
1389
  * useful in every view: a month, a year, or a whole 12-year page.
@@ -1211,31 +1410,32 @@ declare class RelDatePicker extends LitElement {
1211
1410
  */
1212
1411
  private handlePeriodGridFocusin;
1213
1412
  /**
1214
- * Index of the single grid cell carrying tabindex="0". The old expression
1413
+ * Index of the single grid cell carrying tabindex="0": the day last
1414
+ * interacted with (`focusedDate`), else the selection, else today, else the
1415
+ * first enabled day of the month. The old expression
1215
1416
  * (`isSelected || (index === 0 && !value)`) left the grid with ZERO tab
1216
- * stops once the user arrowed around without a selection.
1417
+ * stops once the user arrowed around without a selection; the trailing
1418
+ * fallback keeps it at exactly one by construction.
1217
1419
  *
1218
- * KNOWN GAP (deferred from 1.4.9, needs a ticket covering BOTH calendars):
1219
- * `focusedDate` is read here as the first candidate but is never assigned
1220
- * anywhere in this file, so it is permanently null and the tab stop tracks
1221
- * the SELECTION rather than focus. Tab out of the grid and back and you
1222
- * return to the selected day, not the day you left.
1420
+ * `focusedDate` is written from three places, and all three are needed
1421
+ * (fixed 2026-10-03; the same model as rel-range-picker.getRovingIso):
1422
+ * - moveGridFocusTo, from the DATE, before focus moves. Arrow keys navigate
1423
+ * by date, not by grid index, so Sun 27 Sep + ArrowDown is Sun 4 Oct (the
1424
+ * old index+7 landed on Sun 1 Nov once the view flipped). It cannot be left
1425
+ * to a focus event: lit reuses the same buttons across the month
1426
+ * re-render, so the focused element may not change and NO focus event
1427
+ * fires for the move.
1428
+ * - handleDayClick, so a clicked day holds the Tab stop even where a click
1429
+ * does not focus the button (Safari).
1430
+ * - handleDayGridFocusin, for focus arriving by Tab or any other route.
1223
1431
  *
1224
- * Wiring it from each cell's `@focus` was tried and is NOT sufficient, which
1225
- * is why this is still open. Measured 2026-09-25 by driving real keys: from
1226
- * Sun 27 Sep, one ArrowDown lands focus on Sun 25 Oct instead of Sun 4 Oct,
1227
- * because handleDayKeydown moves by grid INDEX and then lets the view flip
1228
- * month, so index+7 addresses a different date in the new grid. lit reuses
1229
- * the same button elements across that re-render, so the focused element
1230
- * never changes and NO focus event fires — the handler cannot observe the
1231
- * move it is supposed to record.
1232
- *
1233
- * The real fix is to navigate by DATE and set the rove explicitly before
1234
- * focusing, the way rel-range-picker.moveGridFocusTo already does (verified
1235
- * correct in the same run: Sep 27 → Oct 4 → Oct 11, tab stop and focus
1236
- * agreeing at every step). That is a rewrite of this component's arrow-key
1237
- * model, not a contained fix, and it must land on both calendars together or
1238
- * they diverge again.
1432
+ * `focusedDate` counts wherever it sits in the 42-cell grid, out-of-month
1433
+ * cells included. Every date appears in the grid at most once, so this
1434
+ * cannot create a second Tab stop. It only matters after a chevron or a
1435
+ * month/year pick moves the view: the day last interacted with stays the
1436
+ * Tab stop while it is still on screen as a leading/trailing cell. Unavailable
1437
+ * days are aria-disabled, not native disabled, so a Tab stop on one is still
1438
+ * reachable (same as rel-range-picker).
1239
1439
  */
1240
1440
  private getRovingIndex;
1241
1441
  /** Days view: weekday header + the six week rows. */
@@ -1265,9 +1465,10 @@ type RangePickerSizeInput = RangePickerSize | RangePickerSizeAlias;
1265
1465
  * @csspart option - Each quick-select preset button
1266
1466
  * @csspart action - The header's previous and next buttons; they step a month, a year or a 12-year page depending on which view the panel is showing
1267
1467
  * @csspart footer - The Reset / Cancel / Apply button row
1268
- * @cssprop --rel-overlay-fallback-z - z-index of the panel on the NO-POPOVER-API fallback path only. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with rel-select, rel-autocomplete and the other pickers so the whole overlay family can be moved from one place; default 1001.
1468
+ * @csspart unavailable - Day cells that cannot be picked (outside `min-date` / `max-date`). They are `aria-disabled` and stay focusable, so `:disabled` does not match them; style them with `::part(unavailable)`
1469
+ * @cssprop --rel-overlay-fallback-z - z-index of the panel on the **no-Popover-API fallback path only**. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with the other overlays (select, autocomplete, the three pickers) so the whole family can be moved from one place.
1269
1470
  */
1270
- declare class RelRangePicker extends LitElement {
1471
+ declare class RelRangePicker extends RelElement {
1271
1472
  /** Committed range start as an ISO `YYYY-MM-DD` string; empty string when no range is set. */
1272
1473
  from: string;
1273
1474
  /** Committed range end as an ISO `YYYY-MM-DD` string; empty string when no range is set. Always ≥ `from`. */
@@ -1280,7 +1481,7 @@ declare class RelRangePicker extends LitElement {
1280
1481
  minDate: string;
1281
1482
  /** Latest selectable date (ISO `YYYY-MM-DD`); later days are disabled and typed ranges ending after it are rejected. */
1282
1483
  maxDate: string;
1283
- /** Disables the trigger, the icons and the panel controls; the host dims to 60% opacity. */
1484
+ /** Disables the trigger, the icons and the panel controls. The field looks like a disabled `rel-textfield`: the whole field at 0.65 opacity, an elevated-2 box with the accent border, and the value and placeholder in `--rel-text-caption` (changed in 1.5.0: it was the enabled look at 60% opacity). */
1284
1485
  disabled: boolean;
1285
1486
  /** Whether the panel is open. Setting it directly shows/hides the panel but does not seed the draft from `from`/`to` or fire `rel-open-change` — user interaction does both. */
1286
1487
  open: boolean;
@@ -1304,7 +1505,7 @@ declare class RelRangePicker extends LitElement {
1304
1505
  triggerVariant: "default" | "icon";
1305
1506
  /** Shows a clear (×) icon while a range is set; clearing fires `rel-change` with empty `from`/`to`. Only with the default trigger. */
1306
1507
  clearable: boolean;
1307
- /** Stretches the host to fill its parent's width (attribute `fullwidth`),
1508
+ /** Stretches the host to fill its parent's width (attribute `full-width`),
1308
1509
  * mirroring rel-time-picker / rel-date-picker. Off by default so the
1309
1510
  * existing intrinsic-width (`inline-block`) rendering is unchanged. */
1310
1511
  fullWidth: boolean;
@@ -1312,6 +1513,10 @@ declare class RelRangePicker extends LitElement {
1312
1513
  size: RangePickerSizeInput;
1313
1514
  private draftFrom;
1314
1515
  private draftTo;
1516
+ /** Uncommitted text in the panel's From / To inputs; null → the input shows the draft date. */
1517
+ private panelText;
1518
+ /** Parse / availability error per panel input (cleared on the next edit). */
1519
+ private panelError;
1315
1520
  private draftActivePreset;
1316
1521
  /** Which grid the panel shows. Ported from rel-date-picker so the two
1317
1522
  * calendars behave the same; reaching January 2023 was ~44 chevron clicks. */
@@ -1324,6 +1529,8 @@ declare class RelRangePicker extends LitElement {
1324
1529
  private viewYear;
1325
1530
  private viewMonth;
1326
1531
  private isSelectingRange;
1532
+ /** A mouse click picked the start: the To box is marked as next (sandbox design, Range-A). Keyboard focus never moves for it. */
1533
+ private toMarkedNext;
1327
1534
  private hoverDate;
1328
1535
  /** ISO date of the cell that owns the grid's single Tab stop (roving tabindex). */
1329
1536
  private focusedDate;
@@ -1341,7 +1548,7 @@ declare class RelRangePicker extends LitElement {
1341
1548
  private showTopLayer;
1342
1549
  private hideTopLayer;
1343
1550
  private onReposition;
1344
- private onPageScroll;
1551
+ private scrollWatch;
1345
1552
  private updatePopoverPosition;
1346
1553
  private handleClickOutside;
1347
1554
  private handleGlobalKeydown;
@@ -1388,6 +1595,8 @@ declare class RelRangePicker extends LitElement {
1388
1595
  private isSameDay;
1389
1596
  private isDateDisabled;
1390
1597
  private getQuickSelects;
1598
+ /** The preset shown as selected: the one clicked, or else the first whose range equals the draft (sandbox design: picking the days of "Next Week" checks it). */
1599
+ private activePresetLabel;
1391
1600
  private selectQuickSelect;
1392
1601
  private goPrev;
1393
1602
  private goNext;
@@ -1438,30 +1647,67 @@ declare class RelRangePicker extends LitElement {
1438
1647
  private formatDayLabel;
1439
1648
  private getWeekdayLabels;
1440
1649
  private getCalendarDays;
1650
+ /**
1651
+ * Picking an out-of-month day does NOT move the view, unlike rel-calendar
1652
+ * and rel-date-picker (a decision for this component, 2026-10-01). A range
1653
+ * is picked in two clicks, so jumping to the start day's month would send
1654
+ * the user paging back for the end day, and the grid would shift under the
1655
+ * pointer between the clicks. The leading and trailing cells already show
1656
+ * the band and the hover preview across the month boundary, so the range
1657
+ * stays readable without the jump.
1658
+ *
1659
+ * The clicked day takes the Tab stop first, even when it is disabled or out
1660
+ * of the month (see getRovingIso for why that cannot make a second stop).
1661
+ */
1441
1662
  private handleDayClick;
1663
+ private recordDayPick;
1664
+ /** The panel's From / To boxes: real text inputs over the draft. Typing
1665
+ * commits on Enter or blur through the same parser as the trigger
1666
+ * (parseDateInput: MM/DD/YYYY, month names, t / t±N), never to from/to —
1667
+ * Apply still commits the range and Cancel / Escape still discard it. */
1668
+ private renderPanelInput;
1669
+ private handlePanelInput;
1670
+ private handlePanelKeydown;
1671
+ private clearPanelEntry;
1672
+ /** Parse one side into the draft. Empty clears that side; unparseable or
1673
+ * unavailable text stays in the box with an error. A From after the To (or
1674
+ * a To before the From) swaps the two, as typed ranges and grid picks do. */
1675
+ private commitPanelInput;
1442
1676
  private handleDayHover;
1443
1677
  /**
1444
- * The cell that carries tabindex="0". Prefers the last roved date, then the
1445
- * draft start, then today, then the first selectable day — always exactly
1446
- * one Tab stop, even after arrowing away with nothing selected.
1678
+ * The cell that carries tabindex="0". Prefers the day last interacted with,
1679
+ * then the draft start, then today, then the first selectable day — always
1680
+ * exactly one Tab stop, even after arrowing away with nothing selected.
1447
1681
  *
1448
- * `focusedDate` is written by moveGridFocusTo, which sets it from the DATE
1449
- * before moving focus rather than reading it back off a focus event. That
1450
- * ordering is what makes the arrow keys correct across a month boundary —
1451
- * verified 2026-09-25 by driving real keys: Sep 27 → Oct 4 → Oct 11 with the
1452
- * tab stop and focus agreeing at every step, including the view change.
1453
- * rel-date-picker moves by grid INDEX instead and lands on the wrong date
1454
- * there; see the note on its getRovingIndex. Do not "simplify" this to a
1455
- * focus listener — lit reuses the same cell elements across the re-render, so
1456
- * the focused element does not change and no focus event fires.
1682
+ * `focusedDate` is written from three places, and all three are needed
1683
+ * (the same model as rel-date-picker.getRovingIndex; fixed for both
1684
+ * 2026-10-03):
1685
+ * - moveGridFocusTo sets it from the DATE before moving focus. That ordering
1686
+ * is what makes the arrow keys correct across a month boundary (Sep 27 →
1687
+ * Oct 4 → Oct 11). Do not "simplify" this to a focus listener alone: lit
1688
+ * reuses the same cell elements across the month re-render, so the focused
1689
+ * element may not change and no focus event fires.
1690
+ * - handleDayClick, so the clicked day holds the Tab stop.
1691
+ * - handleDayGridFocusin, for focus arriving by Tab or any other route.
1457
1692
  *
1458
- * Still open for BOTH calendars: focus arriving from a click or from Tab does
1459
- * not update the rove, only the arrow keys do. Deferred from 1.4.9 as a
1460
- * shared ticket, since fixing one calendar alone reintroduces the divergence
1461
- * the shared period-page module exists to prevent.
1693
+ * Out-of-month cells: picking one deliberately does NOT move the view (see
1694
+ * handleDayClick), so the clicked leading/trailing cell itself must be able
1695
+ * to hold the Tab stop, or Shift+Tab, Tab after the click would land on a
1696
+ * different day. `focusedDate` therefore counts wherever it sits in the
1697
+ * 42-cell grid. Every date appears in the grid at most once, so this cannot
1698
+ * create a second Tab stop. The fallbacks (draft start, today) stay
1699
+ * in-month, as before. Disabled cells are focusable (aria-disabled), so a
1700
+ * Tab stop on one is still reachable.
1462
1701
  */
1463
1702
  private getRovingIso;
1464
1703
  private handleGridKeydown;
1704
+ /**
1705
+ * Focus arriving by Tab, a click or any other route moves the Tab stop to
1706
+ * that day, so Tab out of the grid and back returns to it. Only catches moves
1707
+ * where the focused ELEMENT changes; a month flip that reuses the focused cell
1708
+ * is covered by moveGridFocusTo setting the date first.
1709
+ */
1710
+ private handleDayGridFocusin;
1465
1711
  private moveGridFocusTo;
1466
1712
  render(): lit_html.TemplateResult<1>;
1467
1713
  }
@@ -1486,14 +1732,15 @@ type ContainerBackground = "light" | "white" | "none";
1486
1732
  * @csspart base - The padded surface `<div>` (same element as `container`)
1487
1733
  * @csspart container - Alias of `base`, kept for existing `::part(container)` selectors
1488
1734
  */
1489
- declare class RelContainer extends LitElement {
1735
+ declare class RelContainer extends RelElement {
1490
1736
  /** Inner padding step: `small` = spacing-xs, `medium` = spacing-md, `large` = spacing-lg. `extra-large` = spacing-2xl is a code-only extension — Figma publishes only the three steps above — and is supported, not deprecated. `sm` / `md` / `lg` / `xl` are alternative spellings and stay supported, normalised to the full words. */
1491
1737
  padding: ContainerPaddingInput;
1492
1738
  /** Adds the shadow-300 elevation box-shadow to the surface. */
1493
1739
  shadow: boolean;
1494
1740
  /**
1495
- * Surface fill: default (page surface) | elevated (raised surface) |
1496
- * transparent. Takes precedence over the deprecated `background` prop.
1741
+ * Surface fill: `"default"` (the default: page surface) | `"elevated"`
1742
+ * (raised surface) | `"transparent"`. Takes precedence over the deprecated
1743
+ * `background` prop.
1497
1744
  */
1498
1745
  surface?: ContainerSurface;
1499
1746
  /** @deprecated Deprecated since 1.4.7 — use `surface` (white→default, light→elevated, none→transparent). */
@@ -1511,7 +1758,7 @@ declare class RelContainer extends LitElement {
1511
1758
  * @csspart base - The rendered rule — restyle thickness, colour or margin here.
1512
1759
  * @cssprop --rel-divider-spacing - Cross-axis margin around the rule; default 16px. Figma specifies none, so set it to `0` for strict parity — the default is kept for back-compat and is planned to become `0` in 3.0.
1513
1760
  */
1514
- declare class RelDivider extends LitElement {
1761
+ declare class RelDivider extends RelElement {
1515
1762
  /** Axis of the rule: `horizontal` spans the full width with vertical margin; `vertical` is a full-height line with horizontal margin (the parent needs a defined height or a flex row). Mirrored to `aria-orientation` on the host. */
1516
1763
  orientation: "horizontal" | "vertical";
1517
1764
  static styles: lit.CSSResult;
@@ -1525,7 +1772,7 @@ declare class RelDivider extends LitElement {
1525
1772
  * row with an 8px gap. Can also be used standalone for a matching actions row.
1526
1773
  * @slot - Footer actions (typically `<rel-button>`s)
1527
1774
  */
1528
- declare class RelDrawerFooter extends LitElement {
1775
+ declare class RelDrawerFooter extends RelElement {
1529
1776
  static styles: lit.CSSResult;
1530
1777
  render(): lit_html.TemplateResult<1>;
1531
1778
  }
@@ -1542,7 +1789,7 @@ type DrawerCloseReason = "backdrop" | "escape" | "drag" | "programmatic";
1542
1789
  * from the bottom, left or right over a scrim, with an optional drag handle
1543
1790
  * (bottom only), a title/description header, a scrollable body and a footer.
1544
1791
  * @slot - Body content of the drawer
1545
- * @slot footer - Actions row; rendered inside a `<rel-drawer-footer>` (right-aligned, 8px gap)
1792
+ * @slot footer - Actions row; rendered inside a `<rel-drawer-footer>` (right-aligned, 8px gap). The footer only renders when this slot has content; its top divider shows only while the body scrolls
1546
1793
  * @csspart overlay - The full-viewport scrim behind the panel
1547
1794
  * @csspart panel - The sliding panel (role="dialog")
1548
1795
  * @csspart handle - The drag-to-close grab bar (bottom drawers only)
@@ -1556,7 +1803,7 @@ type DrawerCloseReason = "backdrop" | "escape" | "drag" | "programmatic";
1556
1803
  * @cssprop --rel-drawer-transition-timing - Slide/scrim animation easing; default `cubic-bezier(0.32, 0.72, 0, 1)`
1557
1804
  * @cssprop --rel-drawer-scrim-opacity - Opacity of the scrim while open; default `1`
1558
1805
  */
1559
- declare class RelDrawer extends LitElement {
1806
+ declare class RelDrawer extends RelElement {
1560
1807
  /** Whether the drawer is shown. Toggle it to open/close; every change fires `rel-open-change`. */
1561
1808
  open: boolean;
1562
1809
  /** Edge the panel slides in from. `bottom` is centred horizontally and supports the drag handle; `left`/`right` are full-height side panels. */
@@ -1593,25 +1840,36 @@ declare class RelDrawer extends LitElement {
1593
1840
  private currentY;
1594
1841
  /** Scrim opacity at rest, sampled once per drag so the fade starts from it. */
1595
1842
  private scrimRestingOpacity;
1596
- private originalBodyOverflow;
1597
- private originalBodyPaddingRight;
1598
1843
  static styles: lit.CSSResult;
1599
1844
  connectedCallback(): void;
1600
1845
  disconnectedCallback(): void;
1846
+ firstUpdated(): void;
1601
1847
  updated(changed: Map<string, any>): void;
1602
1848
  /**
1603
1849
  * The footer divider is conditional: short content (a confirmation message)
1604
1850
  * has no divider, but a body that scrolls does — it is a scroll affordance,
1605
1851
  * not decoration (designer, confirmed). CSS cannot ask whether an element
1606
- * scrolls, so it is measured and reflected onto the host as `body-scrolls`,
1607
- * re-measured whenever the body or its contents resize.
1852
+ * scrolls, so it is measured and reflected onto the host as `body-scrolls`.
1853
+ *
1854
+ * What is observed matters. `.content` is a flex: 1 box in a fixed-height
1855
+ * panel, so its own size does not change when its content grows, and its
1856
+ * only shadow child is the slot, which has no box. So the observer also
1857
+ * watches every element assigned to the default slot (re-collected on
1858
+ * slotchange): content added later, a child growing in place, an image
1859
+ * loading, all resize one of those. Window resizes reach `.content` itself.
1608
1860
  */
1609
1861
  private bodyScrollObserver?;
1610
- private watchBodyScroll;
1862
+ private bodyMeasureQueued;
1863
+ private measureBodyScroll;
1864
+ private queueBodyMeasure;
1865
+ private observeBody;
1611
1866
  private handleOpen;
1612
1867
  private handleClose;
1613
1868
  private lockScroll;
1614
1869
  private unlockScroll;
1870
+ /** Drops a drag in progress when the drawer closes or leaves the DOM, so the
1871
+ * document pointer listeners do not outlive it. */
1872
+ private endDrag;
1615
1873
  private handleKeydown;
1616
1874
  private handleOverlayClick;
1617
1875
  private handleCloseButtonClick;
@@ -1635,15 +1893,41 @@ declare global {
1635
1893
  * names; the full words are accepted as aliases (Trello 4).
1636
1894
  */
1637
1895
  type KbdSize = "sm" | "md" | "lg" | "small" | "medium" | "large";
1896
+ /** Which keyboard convention named keys follow: `auto` detects it, `mac` / `other` force it. */
1897
+ type KbdPlatform = "auto" | "mac" | "other";
1898
+ /** A named key as drawn on one platform: the cap text and the name a screen reader speaks. */
1899
+ interface KeyFace {
1900
+ label: string;
1901
+ name: string;
1902
+ }
1903
+ /**
1904
+ * Platform detection for `platform="auto"`: User-Agent Client Hints first, then
1905
+ * `navigator.platform` (iPadOS reports "MacIntel" there). No `navigator`
1906
+ * (SSR, workers without it) means "other".
1907
+ */
1908
+ declare function detectKbdPlatform(): "mac" | "other";
1909
+ /**
1910
+ * Resolves a key name to its cap text and spoken name. Names are matched
1911
+ * case-insensitively; anything that is not a named key (`K`, `F5`, `⌘`) comes
1912
+ * back exactly as typed.
1913
+ */
1914
+ declare function resolveKbdKey(key: string, platform?: KbdPlatform): KeyFace;
1638
1915
  /**
1639
1916
  * Single key cap (Figma "Kbd", 6316:284) for showing a keyboard key or shortcut
1640
- * inline with text; the key label is the slotted content.
1641
- * @slot - The key label (e.g. `Ctrl`, `K`, `⌘`)
1917
+ * inline with text; the key label is the slotted content, or a named key set
1918
+ * with `key-name`.
1919
+ * @slot - The key label (e.g. `Ctrl`, `K`, `⌘`), rendered exactly as written. Not rendered while `key-name` is set.
1642
1920
  */
1643
- declare class RelKbd extends LitElement {
1644
- /** Key-cap scale (Figma Size): `small` 20px tall / label-sm, `medium` 24px / label-md, `large` 28px / label-lg; 4px radius at every size. `sm` / `md` / `lg` are the legacy short spellings and stay accepted. */
1921
+ declare class RelKbd extends RelElement {
1922
+ /**
1923
+ * New in 1.5.0. A named key, drawn in the platform's convention instead of the slotted text: `mod` (⌘ on Mac, Ctrl elsewhere), `cmd` / `command` / `meta`, `ctrl` / `control`, `alt` / `option` / `opt`, `shift`, `enter` / `return`, `esc` / `escape`, `tab`, `backspace`, `delete`, `space`, `up` / `down` / `left` / `right`, `pageup` / `pagedown`, `home` / `end`, `capslock` (case-insensitive). Mac shows symbols (⌘ ⌃ ⌥ ⇧ ↩ ⎋ ⇥ ⌫ ⌦ ⇞ ⇟ ↖ ↘ ⇪); elsewhere the short words (Ctrl, Alt, Esc, PgUp…); arrows are ↑ ↓ ← → on both and Space is the word on both. The symbol is hidden from assistive technology and the key's full name (Command, Option…) is read instead. Any other value is shown as typed. While set, slotted content is not rendered.
1924
+ */
1925
+ keyName?: string;
1926
+ /** New in 1.5.0. Keyboard convention for `key-name`: `auto` detects Mac / iPhone / iPad from the browser (no `navigator`, as in SSR, counts as `other`), `mac` and `other` force one. Has no effect on slotted text. */
1927
+ platform: KbdPlatform;
1928
+ /** Key-cap scale (Figma Size): `small` 20px tall / label-sm, `medium` 24px / label-md, `large` 28px / label-lg; 4px radius at every size. A single-symbol key (⌘ ⌥ ⇧ ⌃, `mod` when it shows ⌘, arrows…) is a square cap, 20 × 20 / 24 × 24 / 28 × 28px, with the symbol centred (new in 1.5.0). `sm` / `md` / `lg` are the legacy short spellings and stay accepted. */
1645
1929
  size: KbdSize;
1646
- /** Fill / text contrast: `default` = elevated-1 background with label text; `muted` = elevated-2 background with placeholder-tone text for secondary shortcuts. Both carry a 0.5px stroke and the hard 2px bottom edge Figma draws on the key cap. */
1930
+ /** Fill / text contrast: `default` = elevated-1 background with label text; `muted` = elevated-2 background with caption-tone text for secondary shortcuts. Both carry a 0.5px stroke and the hard 2px bottom edge Figma draws on the key cap. */
1647
1931
  variant: "default" | "muted";
1648
1932
  /**
1649
1933
  * Fully rounded corners. Logs a one-time console notice when set.
@@ -1651,10 +1935,14 @@ declare class RelKbd extends LitElement {
1651
1935
  * 16); `pill` is kept working as an alias but logs a one-time notice.
1652
1936
  */
1653
1937
  pill: boolean;
1654
- /** Pressed look: border-strong stroke, 1px downward nudge and (muted variant) a darker fill. */
1938
+ /** Pressed look: border-strong stroke and a 1px downward nudge; the fill stays the variant's. */
1655
1939
  inset: boolean;
1656
1940
  protected willUpdate(changed: PropertyValues<this>): void;
1657
- static styles: lit.CSSResult;
1941
+ static styles: lit.CSSResult[];
1942
+ /** The visible label: the named key's cap text, or the slotted text. */
1943
+ private visibleLabel;
1944
+ private syncSymbol;
1945
+ protected updated(changed: PropertyValues<this>): void;
1658
1946
  render(): lit_html.TemplateResult<1>;
1659
1947
  }
1660
1948
  /**
@@ -1662,23 +1950,25 @@ declare class RelKbd extends LitElement {
1662
1950
  * entry in `items` with "+" separators between them. Slotted <rel-kbd>
1663
1951
  * children may be used instead of the items array.
1664
1952
  *
1665
- * <rel-kbd-group .items=${["Ctrl", "Shift", "P"]} size="medium"></rel-kbd-group>
1953
+ * <rel-kbd-group .items=${["mod", "Shift", "P"]} size="medium"></rel-kbd-group>
1666
1954
  * @slot - Extra content rendered after the generated keys (e.g. hand-written `<rel-kbd>` children)
1667
1955
  * @csspart item - Each generated `<rel-kbd>` key cap
1668
1956
  * @csspart label - The `+` separator between generated keys
1669
1957
  */
1670
- declare class RelKbdGroup extends LitElement {
1671
- /** Key labels to render, one `<rel-kbd>` each, joined by `+` separators. Set as a property or as a JSON array attribute (`items='["Ctrl","K"]'`). */
1958
+ declare class RelKbdGroup extends RelElement {
1959
+ /** Keys to render, one `<rel-kbd>` each, joined by `+` separators (read as "plus"). Set as a property or as a JSON array attribute (`items='["mod","K"]'`). Since 1.5.0 each entry is a `key-name`: named keys (`mod`, `ctrl`, `shift`, `enter`, arrows…, case-insensitive) follow the platform convention, so `"Ctrl"` shows ⌃ on a Mac; use `"mod"` for the primary modifier (⌘ on Mac, Ctrl elsewhere). Anything else is shown as typed. */
1672
1960
  items: string[];
1961
+ /** New in 1.5.0. Passed through to every generated <rel-kbd>: `auto` (default) detects Mac from the browser, `mac` / `other` force a convention for named keys. */
1962
+ platform: KbdPlatform;
1673
1963
  /** Passed through to every generated <rel-kbd>: `small` / `medium` / `large` (legacy `sm` / `md` / `lg` still accepted). */
1674
1964
  size: KbdSize;
1675
1965
  /** Passed through to every generated <rel-kbd>: `default` or `muted`. */
1676
1966
  variant: "default" | "muted";
1677
- static styles: lit.CSSResult;
1967
+ static styles: lit.CSSResult[];
1678
1968
  render(): lit_html.TemplateResult<1>;
1679
1969
  }
1680
1970
 
1681
- declare class RelLayout extends LitElement {
1971
+ declare class RelLayout extends RelElement {
1682
1972
  gap: string;
1683
1973
  columns: number;
1684
1974
  responsive: boolean;
@@ -1716,7 +2006,7 @@ interface Step {
1716
2006
  * @cssprop --rel-stepper-connector-length-md - No longer applies. It parameterised the code-only `size` axis, and Figma has one connector geometry rather than a scale.
1717
2007
  * @cssprop --rel-stepper-connector-length-sm - No longer applies, for the same reason as `-md`.
1718
2008
  */
1719
- declare class RelStepper extends LitElement {
2009
+ declare class RelStepper extends RelElement {
1720
2010
  /** The steps in order, each `{ title: string; subtitle?: string; status: "done" | "progress" | "disabled" }`. Set as a property (or a JSON array attribute); the component never mutates it — update it from the `rel-step-change` handler. */
1721
2011
  steps: Step[];
1722
2012
  /**
@@ -1739,21 +2029,15 @@ declare class RelStepper extends LitElement {
1739
2029
  /**
1740
2030
  * Figma "Type" axis — what the INDICATOR shows: `default` (step number, check
1741
2031
  * mark once done), `dot` (8px dot, no glyph), `number` (always the number) or
1742
- * `icon` (always the check mark).
1743
- *
1744
- * Note `type="dot"` and `connector="dot"` are different axes that both use the
1745
- * word "dot": this one replaces the numbered disc, `connector` dashes the line
1746
- * BETWEEN discs. They are independent and can be combined.
2032
+ * `icon` (always the check mark). Not the same axis as `connector="dot"`, which
2033
+ * dashes the line between steps.
1747
2034
  */
1748
2035
  type: StepperType;
1749
2036
  /**
1750
- * Figma ".Line Types" axis — how the connector line between steps is drawn:
1751
- * `solid` (default, an unbroken 1px rule) or `dot` (a 1px rule dashed 2px on,
1752
- * 2px off).
1753
- *
1754
- * Not to be confused with `type="dot"`, which is the INDICATOR disc. This axis
1755
- * only ever affects the line between steps, and it applies to both
1756
- * orientations.
2037
+ * Figma ".Line Types" axis — how the line BETWEEN steps is drawn: `solid` (an
2038
+ * unbroken 1px rule) or `dot` (a 1px rule dashed 2px on, 2px off). Applies to
2039
+ * both orientations. Not the same axis as `type="dot"`, which is the indicator
2040
+ * disc; the two are independent and can be combined.
1757
2041
  */
1758
2042
  connector: StepperConnector;
1759
2043
  /** Figma "Typel" axis — where the label sits: `top` (top-aligned beside the indicator), `centered` (vertically centred beside it) or `inverted` (indicator above the label). When unset, the deprecated `textAlign` prop drives the layout (`below` ≈ `inverted`, `right` ≈ `centered`). Figma marks Top + Horizontal as an unsupported combination. */
@@ -1791,10 +2075,15 @@ type RadioAlignment = "leading" | "trailing";
1791
2075
  /**
1792
2076
  * Single-select radio group (Figma "Radio"): renders one native radio input per
1793
2077
  * entry in `options`, with optional supporting text, icon or avatar prefix.
2078
+ * Form-associated, so an outer `<label>` (wrapping it, or `for=`) names the
2079
+ * group; clicking that label focuses the selected (else first enabled) option
2080
+ * without selecting anything. It still sets no form value and is not submitted.
1794
2081
  * @csspart base - The `role="radiogroup"` container that lays out the options
1795
2082
  * @csspart label - The visible label text of each option
1796
2083
  */
1797
- declare class RelRadio extends LitElement {
2084
+ declare class RelRadio extends RelElement {
2085
+ /** Form-associated only so an outer `<label>` has a control (see utils/outer-label.ts). No form value is set. */
2086
+ static formAssociated: boolean;
1798
2087
  /** The options to render, in order. Each entry is a `RadioOption` (`value`, `label`,
1799
2088
  * optional `supportingText`, `disabled`, `icon` or `avatar`). Set as a property
1800
2089
  * (`.options=`) or as a JSON attribute. */
@@ -1818,8 +2107,7 @@ declare class RelRadio extends LitElement {
1818
2107
  * verified against the live Published DS file on 2026-09-25, `.Radio
1819
2108
  * Control` measures 20x20 in all 15 Small variants and all 15 Medium ones.
1820
2109
  * v1.4.8 briefly gave Small its own 16px control on the mistaken premise
1821
- * that the ramp was 16/20/24; 1.4.9 reverts that. Do not reintroduce it
1822
- * without re-reading the live file. */
2110
+ * that the ramp was 16/20/24; 1.4.9 reverts that. */
1823
2111
  size: RadioSizeInput;
1824
2112
  /** Supporting-text layout (Figma Density). `"compact"` (default) puts the
1825
2113
  * supporting text on its own line under the row, indented past the control;
@@ -1835,7 +2123,8 @@ declare class RelRadio extends LitElement {
1835
2123
  * Set it to preselect an option; it is updated on every user selection. */
1836
2124
  selectedValue: string | null;
1837
2125
  /** Disables every option (Figma State=Disabled: the whole group at 40% opacity).
1838
- * Individual options can also be disabled via `RadioOption.disabled`. */
2126
+ * Individual options can also be disabled via `RadioOption.disabled`. An ancestor
2127
+ * `<fieldset disabled>` disables the group the same way, without changing this property. */
1839
2128
  disabled: boolean;
1840
2129
  /** Error state (Figma State=Error): rings, dots and supporting text turn danger.
1841
2130
  * Applies to every option while nothing is selected, and only to the selected
@@ -1844,7 +2133,7 @@ declare class RelRadio extends LitElement {
1844
2133
  /** Message rendered under the group (live region) while `error` is set; hidden
1845
2134
  * when `error` is false or the text is empty. */
1846
2135
  errorMessage: string;
1847
- /** Accessible name for the radiogroup (it renders no visible group label). */
2136
+ /** Accessible name for the radiogroup (it renders no visible group label). Unset, an outer `<label>` (wrapping the group, or `for=`) names it instead. */
1848
2137
  fieldAriaLabel?: string;
1849
2138
  /** Value reported to the Angular forms bridge (`registerOnChange` callback) on each
1850
2139
  * change and compared by `writeValue`. Not used for rendering; read `selectedValue`
@@ -1855,6 +2144,15 @@ declare class RelRadio extends LitElement {
1855
2144
  checked: boolean;
1856
2145
  private _onChange;
1857
2146
  private _onTouched;
2147
+ private readonly internals;
2148
+ /** An outer label labels the GROUP, not an option, so its click picks
2149
+ * nothing: it moves focus where Tab would land (the selected option, else
2150
+ * the first enabled one) and the arrow keys choose from there. */
2151
+ private readonly outerLabel;
2152
+ /** Own `disabled`, or an ancestor `<fieldset disabled>`. */
2153
+ private get isDisabled();
2154
+ /** @internal Form-associated lifecycle: an ancestor `<fieldset disabled>` disables the control too (utils/outer-label.ts, 3). */
2155
+ formDisabledCallback(): void;
1858
2156
  static styles: lit.CSSResult;
1859
2157
  protected willUpdate(changed: PropertyValues<this>): void;
1860
2158
  /** Whether the author wrote `density` themselves, as opposed to inheriting
@@ -1890,6 +2188,8 @@ type MenuWidth = "fixed" | "fit";
1890
2188
  type MenuSize = "large" | "medium" | "small";
1891
2189
  interface MenuItem {
1892
2190
  label: string;
2191
+ /** New in 1.5.0. `rel-icon` name drawn before the label on `list` and `arrow` rows, at 20 / 18 / 16px for the large / medium / small sizes. Checkbox and select-option rows ignore it. */
2192
+ icon?: string;
1893
2193
  active?: boolean;
1894
2194
  checked?: boolean;
1895
2195
  disabled?: boolean;
@@ -1901,7 +2201,7 @@ interface MenuItem {
1901
2201
  * currently read slotted children — menus are built from the `items` property.
1902
2202
  * Registered so markup can carry item metadata for future declarative support.
1903
2203
  */
1904
- declare class RelMenuItem extends LitElement {
2204
+ declare class RelMenuItem extends RelElement {
1905
2205
  /** Display text of the item. */
1906
2206
  label: string;
1907
2207
  /** Value that identifies the item (not used by `rel-menu` today). */
@@ -1921,11 +2221,11 @@ declare class RelMenuItem extends LitElement {
1921
2221
  * @cssprop --rel-menu-width - Width of the menu in BOTH sizing modes; default 300px
1922
2222
  * @cssprop --rel-menu-height - Height of the menu when `width="fixed"`, which then scrolls; default 500px. `fit` ignores it and hugs its rows.
1923
2223
  */
1924
- declare class RelMenu extends LitElement {
2224
+ declare class RelMenu extends RelElement {
1925
2225
  /**
1926
2226
  * Row shape (Figma ".Menu Item" Type). `list` (default) = plain rows;
1927
2227
  * `checkbox` = toggling rows with a checkbox; `select-option` = single-select
1928
- * rows with a trailing check.
2228
+ * rows with a leading radio (`role="menuitemradio"`).
1929
2229
  * `arrow` is a code-only extension with no Figma counterpart: rows whose
1930
2230
  * `submenu` opens a flyout. Because it rides this axis, submenus cannot
1931
2231
  * currently be combined with checkbox or select-option rows — see the README.
@@ -1955,20 +2255,38 @@ declare class RelMenu extends LitElement {
1955
2255
  */
1956
2256
  size: MenuSize;
1957
2257
  /** The rows to render, in order. Each entry is a `MenuItem` (`label`, optional
1958
- * `active`, `checked`, `disabled`, `submenu`, `open`). The array is copied on
1959
- * assignment, so mutate and reassign it to update the menu. */
2258
+ * `active`, `checked`, `disabled`, `submenu`, `open`, and, new in 1.5.0, `icon`).
2259
+ * The array is copied on assignment, so mutate and reassign it to update
2260
+ * the menu. */
1960
2261
  items: MenuItem[];
1961
2262
  private focusedIndex;
1962
2263
  /** Index inside the open submenu that virtual focus sits on; -1 = main list. */
1963
2264
  private subFocusedIndex;
2265
+ /** A key has moved the virtual focus since the last pointer press or
2266
+ * movement in the list (or since it lost focus): draws the row ring even
2267
+ * when the list itself is not :focus-visible. */
2268
+ private keyboardNav;
1964
2269
  private _items;
1965
2270
  /** Stable id prefix for aria-activedescendant targets. */
1966
2271
  private menuId;
1967
- static readonly _css = "\n :host {\n color: var(--rel-text-default);\n display: inline-block;\n }\n\n .rel-menu {\n position: relative;\n display: inline-block;\n background: var(--rel-bg-neutral);\n color: var(--rel-text-default);\n /* Audit 17: 8px (border-radius/sm), was 16px; Figma Menu (6326:2931) = shadow/md */\n border-radius: var(--rel-border-radius-sm);\n box-shadow: var(--rel-shadows-md);\n /* Figma's Menu container is 300px around 288px rows. */\n min-width: 300px;\n /* No vertical padding: items fill edge-to-edge so a first/last-item hover\n reaches the menu's top/bottom. NOT overflow:hidden \u2014 that clips the\n type=arrow flyout (position:absolute at left:100%). Instead the first\n and last items carry the outer corner radius so hover backgrounds clip\n to the rounded shape without any overflow. */\n padding: 0;\n outline: none;\n overflow: visible;\n font-family: var(--rel-body-lg-regular-font-family);\n font-weight: var(--rel-body-lg-regular-font-weight);\n font-size: var(--rel-body-lg-regular-font-size);\n line-height: var(--rel-body-lg-regular-line-height);\n letter-spacing: var(--rel-body-lg-regular-letter-spacing);\n }\n\n .rel-menu:focus-visible,\n :host(:focus-visible) .rel-menu {\n outline: 2px solid var(--rel-border-focus);\n outline-offset: 0;\n }\n\n /* Rows follow Figma \".Menu Item\" (6326:616) per size:\n Large 48px = pad 12/12 + body-lg (24 line-height)\n Medium 40px = pad 8/12 + body-md (22)\n Small 32px = pad 4/8 + body-sm (20)\n (audit 17a \u2014 before 1.4.7 every row was 36px: pad 8/12 + a 20px line-height) */\n .rel-menu-item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--rel-spacing-md);\n padding: var(--rel-spacing-sm) var(--rel-spacing-sm);\n line-height: var(--rel-body-lg-regular-line-height);\n min-height: 48px;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n box-sizing: border-box;\n /* Figma strokes every item with --rel-border-subtle. Drawn as an inset\n shadow on the BOTTOM edge only (the pattern rel-menubar already uses)\n so the rows read as a divided list rather than 45 boxed cells, and so\n it costs no layout height. */\n box-shadow: inset 0 calc(-1 * var(--rel-stroke-sm)) 0 var(--rel-border-subtle);\n }\n\n .rel-menu-item:last-child {\n box-shadow: none;\n }\n\n /* Light-DOM component: :host never matches here, so size rules key off the\n reflected attribute on the <rel-menu> element itself. */\n rel-menu[size=\"medium\"] .rel-menu,\n rel-menu[size=\"medium\"] .rel-submenu {\n font-family: var(--rel-body-md-regular-font-family);\n font-weight: var(--rel-body-md-regular-font-weight);\n font-size: var(--rel-body-md-regular-font-size);\n line-height: var(--rel-body-md-regular-line-height);\n letter-spacing: var(--rel-body-md-regular-letter-spacing);\n }\n\n /* Figma Medium / Small frames are fixed at 40 / 32 (content sums to 38 / 28) */\n rel-menu[size=\"medium\"] .rel-menu-item {\n padding: var(--rel-spacing-xs) var(--rel-spacing-sm);\n line-height: var(--rel-body-md-regular-line-height);\n min-height: 40px;\n }\n\n rel-menu[size=\"small\"] .rel-menu,\n rel-menu[size=\"small\"] .rel-submenu {\n font-family: var(--rel-body-sm-regular-font-family);\n font-weight: var(--rel-body-sm-regular-font-weight);\n font-size: var(--rel-body-sm-regular-font-size);\n line-height: var(--rel-body-sm-regular-line-height);\n letter-spacing: var(--rel-body-sm-regular-letter-spacing);\n }\n\n rel-menu[size=\"small\"] .rel-menu-item {\n padding: var(--rel-spacing-2xs) var(--rel-spacing-xs);\n line-height: var(--rel-body-sm-regular-line-height);\n min-height: 32px;\n }\n\n /* First/last items carry the menu's outer radius so their hover fill clips\n to the rounded corners (replaces the removed overflow:hidden). */\n .rel-menu > .rel-menu-item:first-child {\n border-top-left-radius: var(--rel-border-radius-sm);\n border-top-right-radius: var(--rel-border-radius-sm);\n }\n\n .rel-menu > .rel-menu-item:last-child {\n border-bottom-left-radius: var(--rel-border-radius-sm);\n border-bottom-right-radius: var(--rel-border-radius-sm);\n }\n\n /* Figma states (6326:616): Hover = action overlay 0.05, Pressed = 0.10\n (nearest token: overlay-pressed), Active/selected list row = the hover\n overlay + medium label; Checkbox / Select Option rows keep no fill when\n selected. Disabled = 40% opacity. (audit 17) */\n .rel-menu-item:hover {\n background: var(--rel-action-overlay-hover);\n }\n\n .rel-menu-item:active {\n background: var(--rel-action-overlay-pressed);\n }\n\n .rel-menu-item.active {\n background: var(--rel-action-overlay-hover);\n }\n\n /* The active weight bump follows the row's own size, and applies to plain\n rows only \u2014 a checkbox row carries its state in the control, and Figma\n does not bold its label. */\n .rel-menu:not(.type-checkbox) .rel-menu-item.active {\n font-weight: var(--rel-body-lg-medium-font-weight);\n }\n\n rel-menu[size=\"medium\"] .rel-menu:not(.type-checkbox) .rel-menu-item.active {\n font-weight: var(--rel-body-md-medium-font-weight);\n }\n\n /* Small's active row is Figma's mixed tuple: an xs-medium WEIGHT with\n sm-medium METRICS on the sm-regular family. Large and Medium step only\n their weight and keep regular metrics, which is why they are one line\n each and this one is four. */\n rel-menu[size=\"small\"] .rel-menu:not(.type-checkbox) .rel-menu-item.active {\n font-weight: var(--rel-body-xs-medium-font-weight);\n font-size: var(--rel-body-sm-medium-font-size);\n line-height: var(--rel-body-sm-medium-line-height);\n letter-spacing: var(--rel-body-sm-medium-letter-spacing);\n }\n\n .rel-menu.type-checkbox .rel-menu-item.active,\n .rel-menu.type-select-option .rel-menu-item.active {\n background: transparent;\n }\n\n .rel-menu.type-checkbox .rel-menu-item.active:hover,\n .rel-menu.type-select-option .rel-menu-item.active:hover {\n background: var(--rel-action-overlay-hover);\n }\n\n .rel-menu-item.disabled {\n background-color: transparent;\n opacity: 0.4;\n /* Keep the item pointer-interactive so cursor:not-allowed actually\n renders; clicks are already neutralised by the item.disabled guard in\n onItemClick/onItemEnter. pointer-events:none here would make the row\n transparent to the pointer and suppress the cursor. */\n cursor: not-allowed;\n }\n\n /* [data-focused] (presence, not =\"true\"): the ?data-focused boolean\n binding renders the attribute with an EMPTY value, so a =\"true\"\n selector never matches. */\n /* Inset ring: items run the full width of the panel and butt against its\n padding, so an outside ring would cross the panel edge. rel-menu has no\n State=Focused variant in Figma; kept deliberately. */\n .rel-menu-item[data-focused] {\n outline: 2px solid var(--rel-border-focus);\n outline-offset: -2px;\n }\n\n /* Width modes (Figma Type=Fixed / Type=Fit). BOTH are 300px wide \u2014\n COMPONENT \"Type=Fixed\" is 300x500 sizing=FIXED/FIXED and\n COMPONENT \"Type=Fit\" is 300x48 sizing=FIXED/HUG \u2014 so the axis varies the\n HEIGHT, not the width: Fixed pins a scrolling panel, Fit hugs its rows.\n This had it inverted: fit varied the width (max-content) and neither mode\n constrained the height, so the two modes differed on the wrong dimension. */\n .rel-menu.fixed {\n width: var(--rel-menu-width, 300px);\n height: var(--rel-menu-height, 500px);\n overflow-y: auto;\n min-width: 0;\n box-sizing: border-box;\n }\n\n .rel-menu.fit {\n width: var(--rel-menu-width, 300px);\n min-width: 0;\n box-sizing: border-box;\n }\n\n /* Checkbox (Type=Checkbox) / radio (Type=Select Option) rows render the real\n rel-checkbox / rel-radio. pointer-events:none keeps the whole row the click\n target so onItemClick stays the single source of truth. */\n .menu-control {\n pointer-events: none;\n width: 100%;\n }\n\n .menu-label {\n flex: 1 1 auto;\n }\n\n /* Submenu affordance \u2014 same rel-icon chevron the menubar uses.\n\n No opacity here: verified against the live Figma file, \"Chevron Right\"\n is opacity 1 on both the instance and its vector, on every State and\n Size, bound to the same colour variable as the row label beside it.\n The 0.6 that used to sit here dulled it to a mid grey.\n\n The colour is declared rather than inherited on purpose. rel-menu\n renders to the LIGHT DOM (createRenderRoot returns this), so these\n selectors are unscoped globals and an inherited colour would travel\n through the consumer's page cascade \u2014 an app setting a colour on any\n ancestor would have been repainting this chevron. */\n .submenu-chevron {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n color: var(--rel-text-default);\n pointer-events: none;\n }\n\n /* Shortcut / hint text (Figma body-sm-regular, caption tone) */\n .menu-shortcut,\n ::slotted([slot=\"shortcut\"]) {\n font-family: var(--rel-body-sm-regular-font-family);\n font-weight: var(--rel-body-sm-regular-font-weight);\n font-size: var(--rel-body-sm-regular-font-size);\n line-height: var(--rel-body-sm-regular-line-height);\n letter-spacing: var(--rel-body-sm-regular-letter-spacing);\n color: var(--rel-text-caption);\n margin-left: auto;\n padding-left: var(--rel-spacing-md);\n }\n\n \n /* No border: the flyout is the same surface as the parent menu, which has\n none \u2014 overlapping surfaces are separated by shadow. Min-width tracks the\n parent's 300px rather than a narrower 200px. */\n .rel-submenu {\n position: absolute;\n top: calc(-1 * var(--rel-spacing-3xs));\n /* flush against the trigger (no gap) so moving the pointer into the flyout\n never crosses a dead zone that would fire the trigger's mouseleave */\n left: 100%;\n min-width: 300px;\n background: var(--rel-bg-neutral);\n color: var(--rel-text-default);\n border-radius: var(--rel-border-radius-sm);\n box-shadow: var(--rel-shadows-md);\n /* padding:0 + overflow:hidden \u2014 safe here (a submenu has no further nested\n flyout to clip) \u2014 so sub-item hovers fill fully and clip to the corners */\n padding: 0;\n overflow: hidden;\n visibility: hidden;\n pointer-events: none;\n z-index: 20;\n }\n\n .rel-submenu.visible {\n visibility: visible;\n pointer-events: auto;\n }\n\n .rel-submenu .rel-menu-item {\n padding: var(--rel-spacing-sm) var(--rel-spacing-sm);\n }\n\n rel-menu[size=\"medium\"] .rel-submenu .rel-menu-item {\n padding: var(--rel-spacing-xs) var(--rel-spacing-sm);\n }\n\n rel-menu[size=\"small\"] .rel-submenu .rel-menu-item {\n padding: var(--rel-spacing-2xs) var(--rel-spacing-xs);\n }\n ";
2272
+ static readonly _css = "\n :host {\n color: var(--rel-text-default);\n display: inline-block;\n }\n\n .rel-menu {\n position: relative;\n display: inline-block;\n background: var(--rel-bg-neutral);\n color: var(--rel-text-default);\n /* Audit 17: 8px (border-radius/sm), was 16px; Figma Menu (6326:2931) = shadow/md */\n border-radius: var(--rel-border-radius-sm);\n box-shadow: var(--rel-shadows-md);\n /* Figma's Menu container is 300px around 288px rows. */\n min-width: 300px;\n /* No vertical padding: items fill edge-to-edge so a first/last-item hover\n reaches the menu's top/bottom. NOT overflow:hidden \u2014 that clips the\n type=arrow flyout (position:absolute at left:100%). Instead the first\n and last items carry the outer corner radius so hover backgrounds clip\n to the rounded shape without any overflow. */\n padding: 0;\n outline: none;\n overflow: visible;\n font-family: var(--rel-body-lg-regular-font-family);\n font-weight: var(--rel-body-lg-regular-font-weight);\n font-size: var(--rel-body-lg-regular-font-size);\n line-height: var(--rel-body-lg-regular-line-height);\n letter-spacing: var(--rel-body-lg-regular-letter-spacing);\n }\n\n /* No ring on the container (designer, October 2026): while the list has\n keyboard focus, only the current row carries the ring (below). */\n\n /* Rows follow Figma \".Menu Item\" (6326:616) per size:\n Large 48px = pad 12/12 + body-lg (24 line-height)\n Medium 40px = pad 8/12 + body-md (22)\n Small 32px = pad 4/8 + body-sm (20)\n (audit 17a \u2014 before 1.4.7 every row was 36px: pad 8/12 + a 20px line-height) */\n .rel-menu-item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--rel-spacing-md);\n padding: var(--rel-spacing-sm) var(--rel-spacing-sm);\n line-height: var(--rel-body-lg-regular-line-height);\n min-height: 48px;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n user-select: none;\n box-sizing: border-box;\n /* Figma strokes every item with --rel-border-subtle. Drawn as an inset\n shadow on the BOTTOM edge only (the pattern rel-menubar already uses)\n so the rows read as a divided list rather than 45 boxed cells, and so\n it costs no layout height. */\n box-shadow: inset 0 calc(-1 * var(--rel-stroke-sm)) 0 var(--rel-border-subtle);\n }\n\n .rel-menu-item:last-child {\n box-shadow: none;\n }\n\n /* Light-DOM component: :host never matches here, so size rules key off the\n reflected attribute on the <rel-menu> element itself. */\n rel-menu[size=\"medium\"] .rel-menu,\n rel-menu[size=\"medium\"] .rel-submenu {\n font-family: var(--rel-body-md-regular-font-family);\n font-weight: var(--rel-body-md-regular-font-weight);\n font-size: var(--rel-body-md-regular-font-size);\n line-height: var(--rel-body-md-regular-line-height);\n letter-spacing: var(--rel-body-md-regular-letter-spacing);\n }\n\n /* Figma Medium / Small frames are fixed at 40 / 32 (content sums to 38 / 28) */\n rel-menu[size=\"medium\"] .rel-menu-item {\n padding: var(--rel-spacing-xs) var(--rel-spacing-sm);\n line-height: var(--rel-body-md-regular-line-height);\n min-height: 40px;\n }\n\n rel-menu[size=\"small\"] .rel-menu,\n rel-menu[size=\"small\"] .rel-submenu {\n font-family: var(--rel-body-sm-regular-font-family);\n font-weight: var(--rel-body-sm-regular-font-weight);\n font-size: var(--rel-body-sm-regular-font-size);\n line-height: var(--rel-body-sm-regular-line-height);\n letter-spacing: var(--rel-body-sm-regular-letter-spacing);\n }\n\n rel-menu[size=\"small\"] .rel-menu-item {\n padding: var(--rel-spacing-2xs) var(--rel-spacing-xs);\n line-height: var(--rel-body-sm-regular-line-height);\n min-height: 32px;\n }\n\n /* First/last items carry the menu's outer radius so their hover fill clips\n to the rounded corners (replaces the removed overflow:hidden). */\n .rel-menu > .rel-menu-item:first-child {\n border-top-left-radius: var(--rel-border-radius-sm);\n border-top-right-radius: var(--rel-border-radius-sm);\n }\n\n .rel-menu > .rel-menu-item:last-child {\n border-bottom-left-radius: var(--rel-border-radius-sm);\n border-bottom-right-radius: var(--rel-border-radius-sm);\n }\n\n /* Figma states (6326:616): Hover = action overlay 0.05, Pressed = 0.10\n (nearest token: overlay-pressed), Active/selected list row = the hover\n overlay + medium label; Checkbox / Select Option rows keep no fill when\n selected. Disabled = 40% opacity. (audit 17) */\n .rel-menu-item:hover {\n background: var(--rel-action-overlay-hover);\n }\n\n .rel-menu-item:active {\n background: var(--rel-action-overlay-pressed);\n }\n\n .rel-menu-item.active {\n background: var(--rel-action-overlay-hover);\n }\n\n /* The active weight bump follows the row's own size, and applies to plain\n rows only \u2014 a checkbox row carries its state in the control, and Figma\n does not bold its label. */\n .rel-menu:not(.type-checkbox) .rel-menu-item.active {\n font-weight: var(--rel-body-lg-medium-font-weight);\n }\n\n rel-menu[size=\"medium\"] .rel-menu:not(.type-checkbox) .rel-menu-item.active {\n font-weight: var(--rel-body-md-medium-font-weight);\n }\n\n /* Small's active row is Figma's mixed tuple: an xs-medium WEIGHT with\n sm-medium METRICS on the sm-regular family. Large and Medium step only\n their weight and keep regular metrics, which is why they are one line\n each and this one is four. */\n rel-menu[size=\"small\"] .rel-menu:not(.type-checkbox) .rel-menu-item.active {\n font-weight: var(--rel-body-xs-medium-font-weight);\n font-size: var(--rel-body-sm-medium-font-size);\n line-height: var(--rel-body-sm-medium-line-height);\n letter-spacing: var(--rel-body-sm-medium-letter-spacing);\n }\n\n .rel-menu.type-checkbox .rel-menu-item.active,\n .rel-menu.type-select-option .rel-menu-item.active {\n background: transparent;\n }\n\n .rel-menu.type-checkbox .rel-menu-item.active:hover,\n .rel-menu.type-select-option .rel-menu-item.active:hover {\n background: var(--rel-action-overlay-hover);\n }\n\n .rel-menu-item.disabled {\n background-color: transparent;\n opacity: 0.4;\n /* Keep the item pointer-interactive so cursor:not-allowed actually\n renders; clicks are already neutralised by the item.disabled guard in\n onItemClick/onItemEnter. pointer-events:none here would make the row\n transparent to the pointer and suppress the cursor. */\n cursor: not-allowed;\n }\n\n /* [data-focused] (presence, not =\"true\"): the ?data-focused boolean\n binding renders the attribute with an EMPTY value, so a =\"true\"\n selector never matches. */\n /* Inset ring: items run the full width of the panel and butt against its\n padding, so an outside ring would cross the panel edge. rel-menu has no\n State=Focused variant in Figma; kept deliberately \u2014 but ONLY while the\n list has keyboard focus. Virtual focus (data-focused) always sits on\n some row (row 0 at rest, the last clicked row after a click), so an\n ungated ring read as a permanent border on the selected item. */\n /* [data-keyboard]: a key moved the virtual focus. A list that took focus\n without a ring (a mouse press, or rel-popover opening on a pointer\n press with focusVisible: false) does not become :focus-visible on a\n later key press in Chromium, so the arrows would move an invisible\n focus. The flag clears on pointer movement or press and on blur, so\n mouse-only use still never shows the ring. */\n .rel-menu:focus-visible .rel-menu-item[data-focused],\n .rel-menu[data-keyboard]:focus .rel-menu-item[data-focused],\n :host(:focus-visible) .rel-menu-item[data-focused] {\n outline: 2px solid var(--rel-border-focus);\n outline-offset: -2px;\n }\n\n /* Width modes (Figma Type=Fixed / Type=Fit). BOTH are 300px wide \u2014\n COMPONENT \"Type=Fixed\" is 300x500 sizing=FIXED/FIXED and\n COMPONENT \"Type=Fit\" is 300x48 sizing=FIXED/HUG \u2014 so the axis varies the\n HEIGHT, not the width: Fixed pins a scrolling panel, Fit hugs its rows.\n This had it inverted: fit varied the width (max-content) and neither mode\n constrained the height, so the two modes differed on the wrong dimension. */\n .rel-menu.fixed {\n width: var(--rel-menu-width, 300px);\n height: var(--rel-menu-height, 500px);\n overflow-y: auto;\n min-width: 0;\n box-sizing: border-box;\n }\n\n .rel-menu.fit {\n width: var(--rel-menu-width, 300px);\n min-width: 0;\n box-sizing: border-box;\n }\n\n /* Checkbox (Type=Checkbox) / radio (Type=Select Option) rows render the real\n rel-checkbox / rel-radio. pointer-events:none keeps the whole row the click\n target so onItemClick stays the single source of truth. */\n .menu-control {\n pointer-events: none;\n width: 100%;\n }\n\n .menu-label {\n flex: 1 1 auto;\n }\n\n /* Leading item icon (MenuItem.icon): fixed size, never shrinks. */\n .menu-icon {\n flex: none;\n }\n\n /* Submenu affordance \u2014 same rel-icon chevron the menubar uses.\n\n No opacity here: verified against the live Figma file, \"Chevron Right\"\n is opacity 1 on both the instance and its vector, on every State and\n Size, bound to the same colour variable as the row label beside it.\n The 0.6 that used to sit here dulled it to a mid grey.\n\n The colour is declared rather than inherited on purpose. rel-menu\n renders to the LIGHT DOM (createRenderRoot returns this), so these\n selectors are unscoped globals and an inherited colour would travel\n through the consumer's page cascade \u2014 an app setting a colour on any\n ancestor would have been repainting this chevron. */\n .submenu-chevron {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n color: var(--rel-text-default);\n pointer-events: none;\n }\n\n /* Shortcut / hint text (Figma body-sm-regular, caption tone) */\n .menu-shortcut,\n ::slotted([slot=\"shortcut\"]) {\n font-family: var(--rel-body-sm-regular-font-family);\n font-weight: var(--rel-body-sm-regular-font-weight);\n font-size: var(--rel-body-sm-regular-font-size);\n line-height: var(--rel-body-sm-regular-line-height);\n letter-spacing: var(--rel-body-sm-regular-letter-spacing);\n color: var(--rel-text-caption);\n margin-left: auto;\n padding-left: var(--rel-spacing-md);\n }\n\n \n /* No border: the flyout is the same surface as the parent menu, which has\n none \u2014 overlapping surfaces are separated by shadow. Min-width tracks the\n parent's 300px rather than a narrower 200px. */\n .rel-submenu {\n position: absolute;\n top: calc(-1 * var(--rel-spacing-3xs));\n /* flush against the trigger (no gap) so moving the pointer into the flyout\n never crosses a dead zone that would fire the trigger's mouseleave */\n left: 100%;\n min-width: 300px;\n background: var(--rel-bg-neutral);\n color: var(--rel-text-default);\n border-radius: var(--rel-border-radius-sm);\n box-shadow: var(--rel-shadows-md);\n /* padding:0 + overflow:hidden \u2014 safe here (a submenu has no further nested\n flyout to clip) \u2014 so sub-item hovers fill fully and clip to the corners */\n padding: 0;\n margin: 0;\n border: 0;\n overflow: hidden;\n visibility: hidden;\n pointer-events: none;\n z-index: 20;\n }\n\n /* Top layer (Popover API): the flyout escapes every ancestor's overflow\n clip \u2014 the menu's own scroller in width=\"fixed\", cards, rel-modal,\n scrolling sidebars \u2014 and is placed in viewport coordinates by\n positionSubmenu(). It stays a DOM descendant of its row, so moving the\n pointer into it never fires the row's mouseleave. Without the Popover\n API the absolute left:100% placement above still applies. */\n .rel-submenu:popover-open {\n position: fixed;\n inset: auto;\n }\n\n .rel-submenu.visible {\n visibility: visible;\n pointer-events: auto;\n }\n\n .rel-submenu .rel-menu-item {\n padding: var(--rel-spacing-sm) var(--rel-spacing-sm);\n }\n\n rel-menu[size=\"medium\"] .rel-submenu .rel-menu-item {\n padding: var(--rel-spacing-xs) var(--rel-spacing-sm);\n }\n\n rel-menu[size=\"small\"] .rel-submenu .rel-menu-item {\n padding: var(--rel-spacing-2xs) var(--rel-spacing-xs);\n }\n ";
1968
2273
  createRenderRoot(): this;
1969
2274
  connectedCallback(): void;
1970
2275
  disconnectedCallback(): void;
1971
2276
  updated(changedProperties: Map<string, unknown>): void;
2277
+ /** The open flyout and its row while one is showing. */
2278
+ private openSubmenu;
2279
+ /** Outside scroll / resize: follow the row while it is visible, close once
2280
+ * it scrolls out of view (same rule as the select / picker panels). */
2281
+ private submenuScroll;
2282
+ private onSubmenuResize;
2283
+ /** Show / hide each flyout in the top layer to match the rendered state. */
2284
+ private syncSubmenus;
2285
+ private stopTrackingSubmenu;
2286
+ /** Beside the row, flush (no gap, so hover never crosses a dead zone):
2287
+ * to the right when it fits, else flipped to the left; shifted up when it
2288
+ * would run past the bottom; kept 8px inside the viewport. */
2289
+ private positionSubmenu;
1972
2290
  /** Index of a submenu locked open by a click (survives hover-out). -1 = none. */
1973
2291
  private lockedIndex;
1974
2292
  private handleDocumentClick;
@@ -1980,8 +2298,19 @@ declare class RelMenu extends LitElement {
1980
2298
  * virtual focus onto its first entry. */
1981
2299
  private openSubmenuAt;
1982
2300
  private closeSubmenus;
2301
+ /** Keys that move the virtual focus (rel-menu has no type-ahead). */
2302
+ private static readonly NAV_KEYS;
1983
2303
  private onKeydown;
1984
2304
  private renderMenuItem;
2305
+ /**
2306
+ * New in 1.5.0. Moves the virtual focus (the row `aria-activedescendant`
2307
+ * names) to `index`, clamped to the rows; a negative index counts from the
2308
+ * end, so `-1` is the last row. It moves neither DOM focus nor the ring: the
2309
+ * row ring still follows the list's own keyboard focus. A menu button uses it
2310
+ * to open on the first row (Enter, Space, ArrowDown) or the last (ArrowUp).
2311
+ */
2312
+ focusItem(index: number): void;
2313
+ private clearKeyboardNav;
1985
2314
  render(): lit_html.TemplateResult<1>;
1986
2315
  }
1987
2316
 
@@ -1992,7 +2321,7 @@ type MenubarType = "fixed" | "fit";
1992
2321
  * @cssprop --rel-menubar-width - Width of the bar when `type="fixed"`; default 100%
1993
2322
  * @cssprop --rel-menubar-panel-width - Width of the dropdown and submenu panels; default 300px
1994
2323
  */
1995
- declare class RelMenubar extends LitElement {
2324
+ declare class RelMenubar extends RelElement {
1996
2325
  /**
1997
2326
  * Opt-in: actually bind the keys declared via `data-shortcut` on items (e.g. `data-shortcut="Ctrl+Z"`) instead of only displaying them; a "Mod" token means Cmd on Apple platforms and Ctrl elsewhere, and keystrokes that originate in inputs, textareas or contenteditable regions are ignored.
1998
2327
  */
@@ -2013,7 +2342,7 @@ declare class RelMenubar extends LitElement {
2013
2342
  /** Stable id prefix for aria-activedescendant targets (light DOM, so ids
2014
2343
  * must be unique per menubar instance on the page). */
2015
2344
  private menubarId;
2016
- static readonly _css = "\n /* This component renders into LIGHT DOM (createRenderRoot returns this), so\n :host / <slot> don't work here \u2014 the raw <rel-menubar-menu> definitions\n are data holders that must be hidden with a plain selector. Relying on the\n global rel-menubar-menu.css is not enough: it can't cross a consumer's\n shadow boundary AND it's a no-op in the tsup-built package (css:\"copy\"). */\n rel-menubar {\n color: var(--rel-text-default);\n display: block;\n /* Figma Menu Bar / .Menu Bar Item (8699:4, 8698:13) label = body/lg.\n Declared on the host, not on .menubar-container, so the position:fixed\n panels \u2014 siblings of the container, not descendants \u2014 inherit it too. */\n font-family: var(--rel-body-lg-regular-font-family);\n font-weight: var(--rel-body-lg-regular-font-weight);\n font-size: var(--rel-body-lg-regular-font-size);\n line-height: var(--rel-body-lg-regular-line-height);\n letter-spacing: var(--rel-body-lg-regular-letter-spacing);\n }\n\n rel-menubar-menu {\n display: none;\n }\n\n /* Figma Menu Bar (8699:4): bg/fill/neutral/default on border-radius/sm,\n clipping its children, gap spacing/none \u2014 no border and no shadow. */\n .menubar-container {\n display: flex;\n align-items: stretch;\n background: var(--rel-bg-neutral);\n border-radius: var(--rel-border-radius-sm);\n overflow: hidden;\n padding: 0;\n gap: 0;\n min-height: 40px;\n width: fit-content;\n }\n\n /* Type=Fit hugs its triggers (the default); Type=Fixed spans its container.\n Light-DOM component: :host never matches, so these key off the reflected\n attribute on the <rel-menubar> element itself. */\n rel-menubar[type=\"fixed\"] .menubar-container {\n width: var(--rel-menubar-width, 100%);\n }\n\n rel-menubar[type=\"fit\"] .menubar-container {\n width: fit-content;\n }\n\n /* Figma .Menu Bar Item (8698:13): spacing/xs vertical + spacing/md\n horizontal padding around a body/lg label = a 40px trigger. */\n .menubar-trigger {\n display: flex;\n align-items: center;\n justify-content: center;\n align-self: stretch;\n padding: var(--rel-spacing-xs) var(--rel-spacing-md);\n background: transparent;\n border: none;\n cursor: pointer;\n font: inherit;\n color: var(--rel-text-default);\n white-space: nowrap;\n transition: background 0.15s ease;\n user-select: none;\n outline: none;\n }\n\n /* State=Hover / State=Pressed / State=Active / State=Disabled. Active (the\n trigger whose menu is open) is the hover overlay plus a medium label \u2014\n hence the .open class rather than .active, which would collide with the\n :active pressed pseudo at equal specificity. */\n .menubar-trigger:hover:not(.disabled) {\n background: var(--rel-action-overlay-hover);\n }\n\n .menubar-trigger:active:not(.disabled) {\n background: var(--rel-action-overlay-pressed);\n }\n\n .menubar-trigger.open:not(.disabled) {\n background: var(--rel-action-overlay-hover);\n font-weight: var(--rel-body-lg-medium-font-weight);\n }\n\n .menubar-trigger.disabled {\n color: var(--rel-text-disabled);\n background: transparent;\n cursor: not-allowed;\n }\n\n /* Inset ring: the container clips (overflow:hidden), so a positive\n outline-offset would be cut off on the first and last trigger. */\n .menubar-trigger:focus-visible {\n outline: 2px solid var(--rel-border-focus);\n outline-offset: -2px;\n }\n\n /* Figma pairs the Menu Bar with the Menu component (6326:2932) for the\n panel each trigger opens: 300px, bg/fill/neutral/default, border\n radius/sm, shadow/md, clipped \u2014 no border. */\n .menubar-panel,\n .submenu-panel {\n position: fixed;\n width: var(--rel-menubar-panel-width, 300px);\n box-sizing: border-box;\n background: var(--rel-bg-neutral);\n border-radius: var(--rel-border-radius-sm);\n box-shadow: var(--rel-shadows-md);\n padding: 0;\n overflow: hidden;\n display: none;\n outline: none;\n }\n\n .menubar-panel {\n z-index: 1000;\n }\n\n .submenu-panel {\n z-index: 1001;\n }\n\n .menubar-panel.open,\n .submenu-panel.open {\n display: block;\n }\n\n /* Rows follow Figma .Menu Item Large (6326:616): spacing/sm padding all\n round on a body/lg label = 48px, with a border/neutral/subtle rule\n between rows. The rule is an INSET shadow, not a border: Figma strokes\n are drawn inside the 48px frame, so a real border-bottom would push\n every row to 49px. */\n .menu-item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--rel-spacing-md);\n padding: var(--rel-spacing-sm);\n min-height: 48px;\n box-sizing: border-box;\n box-shadow: inset 0 calc(-1 * var(--rel-stroke-sm)) 0 var(--rel-border-subtle);\n cursor: pointer;\n transition: background 0.15s ease;\n user-select: none;\n color: var(--rel-text-default);\n line-height: var(--rel-body-lg-regular-line-height);\n }\n\n .menu-label {\n flex: 1 1 auto;\n }\n\n .menu-item:hover,\n .menu-item.focused {\n background: var(--rel-action-overlay-hover);\n }\n\n .menu-item:active {\n background: var(--rel-action-overlay-pressed);\n }\n\n .menu-item[data-disabled=\"true\"] {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n .menu-separator {\n height: var(--rel-stroke-sm);\n background: var(--rel-border-default);\n margin: 0;\n padding: 0;\n }\n\n /* An explicit data-separator replaces the preceding row's subtle rule\n instead of stacking a second line on top of it. */\n .menu-item:has(+ .menu-separator),\n .menubar-panel > rel-checkbox:has(+ .menu-separator),\n .menubar-panel > rel-radio:has(+ .menu-separator) {\n box-shadow: none;\n }\n\n rel-icon {\n display: inline-flex;\n pointer-events: none;\n }\n\n .menu-submenu-trigger {\n position: relative;\n }\n\n /* Figma draws the 20px chevron at full contrast \u2014 no dimming. */\n .submenu-chevron {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n pointer-events: none;\n }\n\n .menu-shortcut {\n margin-left: auto;\n padding-left: var(--rel-spacing-md);\n display: flex;\n align-items: center;\n gap: var(--rel-spacing-3xs);\n color: var(--rel-text-caption);\n font-family: var(--rel-body-sm-regular-font-family);\n font-weight: var(--rel-body-sm-regular-font-weight);\n font-size: var(--rel-body-sm-regular-font-size);\n line-height: var(--rel-body-sm-regular-line-height);\n letter-spacing: var(--rel-body-sm-regular-letter-spacing);\n }\n\n /* Checkbox / radio rows are real rel-checkbox / rel-radio elements rendered\n straight into the panel, so they carry the .Menu Item row box themselves. */\n .menubar-panel > rel-checkbox,\n .menubar-panel > rel-radio {\n display: block;\n width: 100%;\n min-height: 48px;\n padding: var(--rel-spacing-sm);\n box-sizing: border-box;\n box-shadow: inset 0 calc(-1 * var(--rel-stroke-sm)) 0 var(--rel-border-subtle);\n }\n\n .menubar-panel > rel-checkbox {\n cursor: pointer;\n transition: background 0.15s ease;\n }\n\n .menubar-panel > rel-checkbox:hover {\n background: var(--rel-action-overlay-hover);\n }\n ";
2345
+ static readonly _css = "\n /* This component renders into LIGHT DOM (createRenderRoot returns this), so\n :host / <slot> don't work here \u2014 the raw <rel-menubar-menu> definitions\n are data holders that must be hidden with a plain selector. Relying on the\n global rel-menubar-menu.css is not enough: it can't cross a consumer's\n shadow boundary AND it's a no-op in the tsup-built package (css:\"copy\"). */\n rel-menubar {\n color: var(--rel-text-default);\n display: block;\n /* Figma Menu Bar / .Menu Bar Item (8699:4, 8698:13) label = body/lg.\n Declared on the host, not on .menubar-container, so the position:fixed\n panels \u2014 siblings of the container, not descendants \u2014 inherit it too. */\n font-family: var(--rel-body-lg-regular-font-family);\n font-weight: var(--rel-body-lg-regular-font-weight);\n font-size: var(--rel-body-lg-regular-font-size);\n line-height: var(--rel-body-lg-regular-line-height);\n letter-spacing: var(--rel-body-lg-regular-letter-spacing);\n }\n\n rel-menubar-menu {\n display: none;\n }\n\n /* Figma Menu Bar (8699:4): bg/fill/neutral/default on border-radius/sm,\n clipping its children, gap spacing/none \u2014 no border and no shadow. */\n .menubar-container {\n display: flex;\n align-items: stretch;\n background: var(--rel-bg-neutral);\n border-radius: var(--rel-border-radius-sm);\n overflow: hidden;\n padding: 0;\n gap: 0;\n min-height: 40px;\n width: fit-content;\n }\n\n /* Type=Fit hugs its triggers (the default); Type=Fixed spans its container.\n Light-DOM component: :host never matches, so these key off the reflected\n attribute on the <rel-menubar> element itself. */\n rel-menubar[type=\"fixed\"] .menubar-container {\n width: var(--rel-menubar-width, 100%);\n }\n\n rel-menubar[type=\"fit\"] .menubar-container {\n width: fit-content;\n }\n\n /* Figma .Menu Bar Item (8698:13): spacing/xs vertical + spacing/md\n horizontal padding around a body/lg label = a 40px trigger. */\n .menubar-trigger {\n display: flex;\n align-items: center;\n justify-content: center;\n align-self: stretch;\n padding: var(--rel-spacing-xs) var(--rel-spacing-md);\n background: transparent;\n border: none;\n cursor: pointer;\n font: inherit;\n color: var(--rel-text-default);\n white-space: nowrap;\n transition: background 0.15s ease;\n user-select: none;\n outline: none;\n }\n\n /* State=Hover / State=Pressed / State=Active / State=Disabled. Active (the\n trigger whose menu is open) is the hover overlay plus a medium label \u2014\n hence the .open class rather than .active, which would collide with the\n :active pressed pseudo at equal specificity. */\n .menubar-trigger:hover:not(.disabled) {\n background: var(--rel-action-overlay-hover);\n }\n\n .menubar-trigger:active:not(.disabled) {\n background: var(--rel-action-overlay-pressed);\n }\n\n .menubar-trigger.open:not(.disabled) {\n background: var(--rel-action-overlay-hover);\n font-weight: var(--rel-body-lg-medium-font-weight);\n }\n\n .menubar-trigger.disabled {\n color: var(--rel-text-disabled);\n background: transparent;\n cursor: not-allowed;\n }\n\n /* Inset ring: the container clips (overflow:hidden), so a positive\n outline-offset would be cut off on the first and last trigger. */\n .menubar-trigger:focus-visible {\n outline: 2px solid var(--rel-border-focus);\n outline-offset: -2px;\n }\n\n /* Figma pairs the Menu Bar with the Menu component (6326:2932) for the\n panel each trigger opens: 300px, bg/fill/neutral/default, border\n radius/sm, shadow/md, clipped \u2014 no border. */\n .menubar-panel,\n .submenu-panel {\n position: fixed;\n width: var(--rel-menubar-panel-width, 300px);\n box-sizing: border-box;\n background: var(--rel-bg-neutral);\n border-radius: var(--rel-border-radius-sm);\n box-shadow: var(--rel-shadows-md);\n padding: 0;\n overflow: hidden;\n display: none;\n outline: none;\n }\n\n .menubar-panel {\n z-index: 1000;\n }\n\n .submenu-panel {\n z-index: 1001;\n }\n\n .menubar-panel.open,\n .submenu-panel.open {\n display: block;\n }\n\n /* Rows follow Figma .Menu Item Large (6326:616): spacing/sm padding all\n round on a body/lg label = 48px, with a border/neutral/subtle rule\n between rows. The rule is an INSET shadow, not a border: Figma strokes\n are drawn inside the 48px frame, so a real border-bottom would push\n every row to 49px. */\n .menu-item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--rel-spacing-md);\n padding: var(--rel-spacing-sm);\n min-height: 48px;\n box-sizing: border-box;\n box-shadow: inset 0 calc(-1 * var(--rel-stroke-sm)) 0 var(--rel-border-subtle);\n cursor: pointer;\n transition: background 0.15s ease;\n user-select: none;\n color: var(--rel-text-default);\n line-height: var(--rel-body-lg-regular-line-height);\n }\n\n .menu-label {\n flex: 1 1 auto;\n }\n\n .menu-item:hover,\n .menu-item.focused {\n background: var(--rel-action-overlay-hover);\n }\n\n .menu-item:active {\n background: var(--rel-action-overlay-pressed);\n }\n\n .menu-item[data-disabled=\"true\"] {\n opacity: 0.4;\n cursor: not-allowed;\n }\n\n /* border-subtle, like rel-menu's Figma-bound row strokes (Menu Bar binds\n no separator colour of its own). */\n .menu-separator {\n height: var(--rel-stroke-sm);\n background: var(--rel-border-subtle);\n margin: 0;\n padding: 0;\n }\n\n /* An explicit data-separator replaces the preceding row's subtle rule\n instead of stacking a second line on top of it. */\n .menu-item:has(+ .menu-separator),\n .menubar-panel > rel-checkbox:has(+ .menu-separator),\n .menubar-panel > rel-radio:has(+ .menu-separator) {\n box-shadow: none;\n }\n\n rel-icon {\n display: inline-flex;\n pointer-events: none;\n }\n\n .menu-submenu-trigger {\n position: relative;\n }\n\n /* Figma draws the 20px chevron at full contrast \u2014 no dimming. */\n .submenu-chevron {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n pointer-events: none;\n }\n\n .menu-shortcut {\n margin-left: auto;\n padding-left: var(--rel-spacing-md);\n display: flex;\n align-items: center;\n gap: var(--rel-spacing-3xs);\n color: var(--rel-text-caption);\n font-family: var(--rel-body-sm-regular-font-family);\n font-weight: var(--rel-body-sm-regular-font-weight);\n font-size: var(--rel-body-sm-regular-font-size);\n line-height: var(--rel-body-sm-regular-line-height);\n letter-spacing: var(--rel-body-sm-regular-letter-spacing);\n }\n\n /* Checkbox / radio rows are real rel-checkbox / rel-radio elements rendered\n straight into the panel, so they carry the .Menu Item row box themselves. */\n .menubar-panel > rel-checkbox,\n .menubar-panel > rel-radio {\n display: block;\n width: 100%;\n min-height: 48px;\n padding: var(--rel-spacing-sm);\n box-sizing: border-box;\n box-shadow: inset 0 calc(-1 * var(--rel-stroke-sm)) 0 var(--rel-border-subtle);\n }\n\n .menubar-panel > rel-checkbox {\n cursor: pointer;\n transition: background 0.15s ease;\n }\n\n .menubar-panel > rel-checkbox:hover {\n background: var(--rel-action-overlay-hover);\n }\n ";
2017
2346
  createRenderRoot(): this;
2018
2347
  connectedCallback(): void;
2019
2348
  disconnectedCallback(): void;
@@ -2052,7 +2381,7 @@ declare class RelMenubar extends LitElement {
2052
2381
  /**
2053
2382
  * Declarative menu definition for `<rel-menubar>`: a hidden (`display: none`) light-DOM holder whose children (`data-item`, `data-separator`, `data-submenu`, `data-checkbox`, `data-radio-group`) the parent menubar reads and renders into a dropdown panel.
2054
2383
  */
2055
- declare class RelMenubarMenu extends LitElement {
2384
+ declare class RelMenubarMenu extends RelElement {
2056
2385
  /** Text of the trigger button the parent menubar renders for this menu. */
2057
2386
  label: string;
2058
2387
  /**
@@ -2085,7 +2414,7 @@ type OtpSize = "small" | "medium" | "large" | "sm" | "md" | "lg";
2085
2414
  * @csspart separator - The dash rendered between cell groups
2086
2415
  * @csspart hidden-input - The hidden form input rendered when `name` is set
2087
2416
  */
2088
- declare class RelOtp extends LitElement {
2417
+ declare class RelOtp extends RelElement {
2089
2418
  /** The concatenated code (all cells joined). Set it to fill the cells programmatically; it never exceeds `length` characters. */
2090
2419
  value: string;
2091
2420
  /** Total number of cells (characters in the code). */
@@ -2125,7 +2454,7 @@ declare class RelOtp extends LitElement {
2125
2454
  ariaLabel: string | null;
2126
2455
  /** Error styling (danger border on every cell) and `aria-invalid="true"`. */
2127
2456
  invalid: boolean;
2128
- /** Renders the dash separator between groups. Boolean attribute: its presence means true — set the property to `false` to hide separators. Even when on, separators appear only if `length` divides evenly by `group-size`; see `groupSize`. */
2457
+ /** Renders the dash separator between groups. Presence or `""` means true; `show-separator="false"` (or the property set to `false`) hides them. Even when on, separators appear only if `length` divides evenly by `group-size`; see `groupSize`. */
2129
2458
  showSeparator: boolean;
2130
2459
  /** Cell size on the shared field scale (square, border-box): `small` 32px, `medium` 40px, `large` 48px (the Figma OTP Input cell). `sm` / `md` / `lg` are deprecated aliases of the full words. */
2131
2460
  size: OtpSize;
@@ -2166,418 +2495,85 @@ declare global {
2166
2495
  }
2167
2496
  }
2168
2497
 
2498
+ type SelectState = "default" | "error" | "disabled";
2499
+ type SelectSize = "small" | "medium" | "large";
2500
+ type SelectBorder = "rounded" | "sharp";
2501
+ /** Alias of the shared field type — kept so existing imports keep resolving. */
2502
+ type SelectLabelStyle = FieldLabelStyle;
2503
+ interface SelectOptionObject {
2504
+ value: string;
2505
+ label?: string;
2506
+ supportingText?: string;
2507
+ /** rel-icon name rendered as a prefix (leading icon) on the menu item. */
2508
+ icon?: string;
2509
+ }
2510
+ type SelectOption = string | SelectOptionObject;
2169
2511
  /**
2170
- * Page navigation (Figma "Pagination"): prev/next ghost buttons with numbered pages, a compact "Page X of Y" readout, or a "1–10 of 50" item-range readout. Use it under tables and lists that load one page of results at a time; it never fetches data itself — listen for `rel-change`.
2171
- * @csspart base - The `role="navigation"` root of the extended layout (page buttons plus the optional "Viewing page" label).
2172
- * @csspart label - The `role="navigation"` root row of the count-text layout (item-range text plus prev/next nav).
2512
+ * `<rel-select>` — combobox-style dropdown (Figma "Dropdown"): single- or multi-select
2513
+ * with an optional in-panel search field and width-adaptive selection chips.
2514
+ * @csspart label - The visible label above the trigger (default label style)
2515
+ * @csspart trigger - The button that opens the panel; styled like a text field
2516
+ * @csspart value - The value area: the selected label or placeholder inside the trigger. In multi-select with chips (since 1.5.0) it is the chip overlay, a sibling laid over the trigger's value area rather than a child of it
2517
+ * @csspart helper - The supporting / error text below the trigger
2518
+ * @csspart panel - The dropdown panel (a `role="group"` holding the search, Select-all and the options listbox; same node as `list`)
2519
+ * @csspart list - Alias of `panel`
2520
+ * @csspart input - The search input inside the panel
2521
+ * @csspart option - A single-select option row
2522
+ * @csspart option-label - A single-select row's first line (the option label)
2523
+ * @csspart option-supporting-text - A single-select row's optional second line; rendered only when the option supplies `supportingText`
2524
+ * @cssprop --rel-field-label-reserve - Height held for the label row whenever no label renders above the trigger — `labelStyle="none"`, and `labelStyle="floating"` where the label sits inside the trigger — so the trigger still lines up with labelled siblings in a form row. Defaults to that size's label line-height plus the wrapper gap (24px at `small`, 26px at `medium` and `large`). Set to `0` to collapse the row entirely (toolbars, table cells).
2525
+ * @cssprop --rel-select-bg - Background of the trigger, the panel and its sticky header. No longer paints behind the floating label — see `--rel-select-surface`.
2526
+ * @cssprop --rel-select-bg-disabled - Trigger background when `state="disabled"`
2527
+ * @cssprop --rel-select-surface - Colour of the strip the floating label lays over the trigger's top border and focus ring, so the label appears to sit in a notch. It overshoots the band by 2px on the page side, so it must match the surface the field sits on. Defaults to `--rel-select-bg`. Set it when the field sits on a surface that is not the trigger's own fill, otherwise the strip shows as a short dash of the wrong colour. Mirrors `--rel-textfield-surface`.
2528
+ * @cssprop --rel-select-surface-disabled - The same strip while `state="disabled"`, where the trigger fill changes. Defaults to `--rel-select-bg-disabled`.
2529
+ * @cssprop --rel-select-border-default - Trigger border colour at rest
2530
+ * @cssprop --rel-select-border-disabled - Trigger border colour when `state="disabled"`
2531
+ * @cssprop --rel-select-border-focus - Trigger border colour on hover, while focused (where it also thickens to 2px) and while the panel is open
2532
+ * @cssprop --rel-select-border-error - Trigger border colour when `state="error"`
2533
+ * @cssprop --rel-select-radius - Trigger corner radius when `border="rounded"`
2534
+ * @cssprop --rel-select-icon-color - Colour of the trigger icons, the search icon and option prefix icons
2535
+ * @cssprop --rel-select-label-color - Colour of the static label and of the floated label
2536
+ * @cssprop --rel-select-value-color - Colour of the selected value, the search text, option labels and the check mark
2537
+ * @cssprop --rel-select-placeholder-color - Placeholder colour (trigger, resting floating label, search field, empty-list message)
2538
+ * @cssprop --rel-select-placeholder-disabled-color - Placeholder colour when disabled
2539
+ * @cssprop --rel-select-supporting-color - Supporting text colour (non-error)
2540
+ * @cssprop --rel-select-menu-border - Panel border colour. The panel is borderless by design and only draws this on dark surfaces, where its shadow alone can't separate it from the page.
2541
+ * @cssprop --rel-select-menu-radius - Panel corner radius
2542
+ * @cssprop --rel-select-menu-shadow - Panel box-shadow
2543
+ * @cssprop --rel-select-panel-min-width - Smallest width of the dropdown panel. The panel is as wide as the trigger; set this when the trigger is narrower than its options need (a scrollbar included), and the panel grows past the trigger's right edge instead of squeezing them. Unset by default, so the panel matches the trigger. New in 1.5.0.
2544
+ * @cssprop --rel-select-search-border - Border colour of the search field inside the panel
2545
+ * @cssprop --rel-select-divider - Hairline under the search field, the Select-all row and every option row
2546
+ * @cssprop --rel-select-option-hover - Background of the hovered / keyboard-highlighted / selected option and Select-all row
2547
+ * @cssprop --rel-select-option-pressed - Background of an option row while it is being pressed
2548
+ * @cssprop --rel-overlay-fallback-z - z-index of the panel on the **no-Popover-API fallback path only**. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with the other overlays (select, autocomplete, the three pickers) so the whole family can be moved from one place.
2173
2549
  */
2174
- declare class RelPagination extends LitElement {
2175
- /** Figma "Type": `extended` = prev/next + numbered page buttons (auto-collapses to compact under 480px container width), `compact` = prev/next + "Page X of Y", `count-text` = "start–end of total" item range + prev/next around the current page number. */
2176
- type: "extended" | "compact" | "count-text";
2177
- /** Controlled current page (1-based). When set, the element only emits `rel-change` and waits for the parent to update `page`; leave unset for uncontrolled behaviour. */
2178
- page?: number;
2179
- /** Starting page for uncontrolled use (ignored when `page` is set). Attribute `defaultPage`, also read once as `default-page` on connect. */
2180
- defaultPage: number;
2181
- /** Total number of pages. `0` (the default) lets `totalItems` / `perPage` compute the page count instead. Attribute `totalPages`, also read once as `total-pages` on connect. */
2182
- totalPages: number;
2183
- /** Total item count; with `perPage` it computes the page count when `totalPages` is `0` and is the "of N" figure in the count-text readout. Attribute `totalItems`, also read once as `total-items` on connect. */
2184
- totalItems: number;
2185
- /** Items per page, used to compute the page count from `totalItems` and the "start–end" range in the count-text readout. Attribute `perPage`, also read once as `per-page` on connect. */
2186
- perPage: number;
2187
- /** Shows a "Viewing page X of Y" caption under the extended layout (ignored by the other types). */
2550
+ declare class RelSelect extends RelElement {
2551
+ /** Visible label. Rendered above the trigger (`labelStyle="default"`) or floating inside it; omitted under `labelStyle="none"`. */
2552
+ label: string;
2188
2553
  /**
2189
- * DELIBERATE DIVERGENCE — do not flip this default to `true`. Figma's
2190
- * `Type=Extended` has no boolean for the caption: the "Viewing page 1 of 10"
2191
- * text is a fixed child of both Extended variants and is load-bearing for
2192
- * their 78px height (40 + spacing-md + 22). Here it stays opt-in. Ruled by
2193
- * the user: our behaviour stands and the DESIGN FILE follows our docs.
2194
- * Defaulting it on would silently add 38px to every existing extended
2195
- * pagination.
2554
+ * Label presentation: `default` static label above the trigger; `floating` label
2555
+ * rests in the value area and floats onto the border when focused, open or filled;
2556
+ * `none` renders no label (supply `fieldAriaLabel` / `fieldAriaLabelledby`).
2196
2557
  */
2197
- showLabel: boolean;
2198
- /** Dims the whole control to 40% opacity, shows a `not-allowed` cursor and ignores every page change. */
2199
- disabled: boolean;
2200
- /** Reserved for link-mode pagination. Currently accepted but not rendered: every page control is a `<button>`, so navigate in your `rel-change` handler instead. */
2201
- hrefTemplate: string;
2202
- /** Accessible name of the `role="navigation"` landmark (attribute `ariaLabel`). Set it when a page contains more than one pagination control. */
2203
- ariaLabel: string;
2204
- private currentPage;
2205
- static styles: lit.CSSResult;
2206
- connectedCallback(): void;
2207
- updated(changedProperties: Map<string, unknown>): void;
2208
- private get activePage();
2209
- private get computedTotalPages();
2210
- private generatePageNumbers;
2211
- /** Abbreviate large page numbers (368178 → "368k") so buttons stay compact.
2212
- Exact below 10,000; floored to k up to 1M; then M with one decimal. */
2213
- private formatPageLabel;
2214
- private handlePageChange;
2215
- private renderChevronLeft;
2216
- private renderChevronRight;
2217
- private renderNavBtn;
2218
- private renderExtended;
2219
- private renderCompact;
2220
- private renderCountText;
2221
- render(): lit_html.TemplateResult<1>;
2222
- }
2223
-
2224
- type SideType = "top" | "right" | "bottom" | "left";
2225
- type AlignType = "start" | "center" | "end";
2226
- /**
2227
- * Click-to-open floating panel anchored to a trigger (Figma "Popover"): the first child is the trigger, the `content` slot is the panel, which is promoted to the browser top layer and positioned in viewport coordinates.
2228
- * @slot - The trigger: the first slotted element toggles the panel on pointer down / Enter / Space and receives `aria-haspopup`, `aria-expanded` and `aria-controls` (a bare `<div>` / `<span>` also gets `tabindex="0"` and `role="button"`).
2229
- * @slot content - The panel body, usually a `<rel-popover-content>`; rendered inside the fixed, top-layer wrapper only while open.
2230
- * @csspart arrow - The 14×14 chevron box rendered on the panel edge facing the trigger when `arrow` is set (absent otherwise).
2231
- * @cssprop --rel-popover-arrow-bg - Fill of the `arrow` chevron (default `--rel-bg-neutral`, matching `<rel-popover-content>`).
2232
- * @cssprop --rel-popover-arrow-border - **Deprecated.** Stroke of the `arrow` chevron's two slanted edges. The panel no longer draws a border — overlapping surfaces are separated by shadow — so this defaults to `transparent` and the chevron has no edge. Set it to restore a stroke.
2233
- */
2234
- declare class RelPopover extends LitElement {
2235
- /** Controlled open state. Leave unset for uncontrolled mode; once set (true or false) the component only emits `rel-open-change` and you must update `open` yourself. */
2236
- open?: boolean;
2237
- /** Initial state in uncontrolled mode (attribute `defaultopen`); ignored when `open` is set. */
2238
- defaultOpen: boolean;
2239
- /** Blocks trigger interaction and dims the host to 60% opacity; does not close an already-open panel. */
2240
- disabled: boolean;
2241
- /** Which side of the trigger the panel opens on: `top`, `right`, `bottom` (default) or `left`. The panel is clamped 8px inside the viewport but never flips to another side. */
2242
- side: SideType;
2243
- /** Alignment along the cross axis: `start` (left / top edge of the trigger), `center` (default) or `end` (right / bottom edge). */
2244
- align: AlignType;
2245
- /** Gap between trigger and panel in pixels (attribute `sideoffset`, default 8). With `arrow` the effective gap is at least 13px. */
2246
- sideOffset: number;
2247
- /** Extra shift along the alignment axis in pixels (attribute `alignoffset`, default 0); positive moves right / down. */
2248
- alignOffset: number;
2249
- /** Keep Tab / Shift+Tab cycling inside the panel while open (attribute `trapfocus`); use for forms. */
2250
- trapFocus: boolean;
2251
- /** Close on Escape (attribute `closeonescape`, default true; switch off via the property, e.g. `el.closeOnEscape = false`). */
2252
- closeOnEscape: boolean;
2253
- /** Close on a pointer-down outside the trigger and panel (attribute `closeonoutsideclick`, default true; switch off via the property). Also lets another popover's opening dismiss this one. */
2254
- closeOnOutsideClick: boolean;
2558
+ labelStyle: SelectLabelStyle;
2255
2559
  /**
2256
- * Opt-in pointer (audit 23a): a 14×11 triangle on the panel edge that faces
2257
- * the trigger — the Figma Popover "Chevron" — following `side` and tracking
2258
- * the trigger's centre along the cross axis (clamped inside the panel).
2259
- * It is drawn with the panel's tokens (`--rel-popover-arrow-bg` /
2260
- * `--rel-popover-arrow-border`, defaulting to rel-popover-content's
2261
- * background and border). With the arrow on, the panel keeps at least the
2262
- * arrow's height plus 2px away from the trigger so the tip never overlaps
2263
- * it; larger `sideOffset` values still apply. Default off — existing
2264
- * popovers render exactly as before.
2560
+ * Accessible name for the trigger. Required when `labelStyle="none"`, since no
2561
+ * `<label>` renders and a placeholder is not a valid naming mechanism.
2265
2562
  */
2266
- arrow: boolean;
2267
- /** Figma Popover (6338:563) chevron: 14 wide × 11 tall. */
2268
- private static readonly ARROW_WIDTH;
2269
- private static readonly ARROW_HEIGHT;
2270
- private internalOpen;
2271
- private contentStyle;
2272
- private contentWrapper;
2273
- private triggerEl;
2274
- private returnFocusEl;
2275
- private uniqueId;
2276
- static styles: lit.CSSResult;
2277
- connectedCallback(): void;
2278
- disconnectedCallback(): void;
2279
- firstUpdated(): void;
2280
- updated(changed: Map<string, unknown>): void;
2281
- private isControlled;
2282
- private get activeOpen();
2283
- private setOpen;
2284
- private onHostPointerDown;
2285
- private onHostKeyDown;
2286
- private onOpen;
2287
- private onClose;
2563
+ fieldAriaLabel?: string;
2564
+ /** IDREF list of external element(s) naming the trigger. Wins over `fieldAriaLabel`. */
2565
+ fieldAriaLabelledby?: string;
2288
2566
  /**
2289
- * Light-dismiss every other open popover that isn't an ancestor of this
2290
- * one (nested popovers keep their parent open). This is what the outside-
2291
- * pointerdown listener would do for a click on our trigger — except the
2292
- * trigger handler stops that pointerdown in the capture phase, and a
2293
- * keyboard (Enter/Space) or programmatic open never produces one at all.
2294
- * Honors each popover's own `closeOnOutsideClick` setting.
2295
- */
2296
- private closeOtherPopovers;
2297
- private showNativePopover;
2298
- private hideNativePopover;
2299
- private addGlobalListeners;
2300
- private removeGlobalListeners;
2301
- private onOutsidePointerDown;
2302
- private onKeyDown;
2303
- private focusFirst;
2304
- private restoreFocus;
2305
- private trapFocusLoop;
2306
- private getFocusable;
2307
- private updatePosition;
2308
- private onTriggerSlotChange;
2309
- private applyTriggerAria;
2310
- render(): lit_html.TemplateResult<1>;
2311
- }
2312
- declare global {
2313
- interface HTMLElementTagNameMap {
2314
- "rel-popover": RelPopover;
2315
- }
2316
- }
2317
-
2318
- /** Figma Size axis, spelled out. */
2319
- type PopoverContentSize = "small" | "medium" | "large";
2320
- /** Alternative spellings, not deprecations — the short names keep working. */
2321
- type PopoverContentSizeAlias = "sm" | "md" | "lg";
2322
- type PopoverContentSizeInput = PopoverContentSize | PopoverContentSizeAlias;
2323
- /**
2324
- * Styled panel body for `<rel-popover>` (Figma "Popover" surface): neutral background, 1px border, 8px radius, shadow-300, max-width 400px, with heading / paragraph typography for slotted content. Place it in the popover's `content` slot; any other element works there too.
2325
- * @slot - The panel content (headings, paragraphs, form controls, buttons).
2326
- * @csspart content - The padded `role="dialog"` box that carries the surface, border, shadow and size padding.
2327
- */
2328
- declare class RelPopoverContent extends LitElement {
2329
- /** Accessible name applied to the inner `role="dialog"` box (attribute `aria-label`); describe what the panel is for. */
2330
- ariaLabel: string;
2331
- /** Figma Size axis: panel padding 12px (`small`), 16px (`medium`, default) or 20px (`large`). `sm` / `md` / `lg` are alternative spellings and stay supported — they are normalised to the full words, not deprecated. */
2332
- size: PopoverContentSizeInput;
2333
- /** Alternative spellings, NOT deprecations — no console notice. Normalising
2334
- * keeps the reflected attribute canonical, so the :host([size=…]) rules only
2335
- * need the full words. */
2336
- protected willUpdate(changed: Map<string, unknown>): void;
2337
- static styles: lit.CSSResult;
2338
- render(): lit_html.TemplateResult<1>;
2339
- }
2340
-
2341
- type ProgressMode = "linear-determinate" | "linear-indeterminate" | "circular-determinate" | "circular-indeterminate";
2342
- /**
2343
- * Progress indicator (Figma "Progress"): a linear bar or a circular ring, each either value-driven (determinate) or looping (indeterminate).
2344
- * @csspart track - The linear track `<div>` that carries `role="progressbar"` (linear modes only)
2345
- * @csspart indicator - The filled / sliding bar inside the linear track (linear modes only)
2346
- */
2347
- declare class RelProgress extends LitElement {
2348
- /** Shape and behaviour: `linear-*` is a full-width 2px bar, `circular-*` a 36px ring; `*-determinate` fills to `value`, `*-indeterminate` loops an animation and ignores `value`. */
2349
- mode: ProgressMode;
2350
- /** Completion percentage 0–100 for the determinate modes; out-of-range values are clamped before rendering and before `aria-valuenow`. */
2351
- value: number;
2352
- /** Accessible name of the progressbar (not rendered visually); defaults to "Progress" for determinate and "Loading" for indeterminate modes. */
2353
- label: string;
2354
- private radius;
2355
- private circumference;
2356
- static styles: lit.CSSResult;
2357
- private get safeValue();
2358
- private get accessibleName();
2359
- private renderLinearDeterminate;
2360
- private renderLinearIndeterminate;
2361
- private renderCircularDeterminate;
2362
- private renderCircularIndeterminate;
2363
- render(): lit_html.TemplateResult<1>;
2364
- }
2365
-
2366
- /**
2367
- * Flex container that lays out direct-child `<rel-resizable-panel>`s along one axis and lets the user drag or keyboard-nudge the `<rel-resizable-handle>` between two neighbours to trade percentage width/height between them.
2368
- * @slot - The panels and handles, as direct children in display order (panel, handle, panel, …)
2369
- * @fires rel-resize - Fired continuously while a handle is dragged. Detail: `{ direction, sizes: number[] }` with one percentage per panel.
2370
- * @fires rel-resize-end - Fired once when a drag ends (pointerup) and after every keyboard resize step. Detail: `{ direction, sizes: number[] }` with one percentage per panel.
2371
- */
2372
- declare class RelResizableGroup extends LitElement {
2373
- /** Layout axis: `horizontal` places panels side by side and resizes widths, `vertical` stacks them and resizes heights; also pushed to every child handle's `orientation`. */
2374
- direction: "horizontal" | "vertical";
2375
- private panels;
2376
- private handles;
2377
- private isDragging;
2378
- private activeHandleIndex;
2379
- private startPointerPos;
2380
- private startSizes;
2381
- static styles: lit.CSSResult;
2382
- connectedCallback(): void;
2383
- disconnectedCallback(): void;
2384
- firstUpdated(): void;
2385
- private initializeLayout;
2386
- private calculateInitialSizes;
2387
- private onPointerDown;
2388
- private startDrag;
2389
- private onPointerMove;
2390
- private onPointerUp;
2391
- private applyDragResize;
2392
- private onKeyDown;
2393
- private applyKeyboardResize;
2394
- private applySizes;
2395
- private dispatchResizeEvent;
2396
- private addGlobalListeners;
2397
- private removeGlobalListeners;
2398
- render(): lit_html.TemplateResult<1>;
2399
- }
2400
- declare global {
2401
- interface HTMLElementTagNameMap {
2402
- "rel-resizable-group": RelResizableGroup;
2403
- }
2404
- }
2405
-
2406
- /**
2407
- * One resizable region inside a `<rel-resizable-group>`: a padded, scrollable (`overflow: auto`) flex column whose size along the group axis is a percentage managed by the group.
2408
- * @slot - The panel's content; slotted `h1`–`h4` get the headings-md role
2409
- */
2410
- declare class RelResizablePanel extends LitElement {
2411
- /** Initial size as a percentage of the group; panels without one share whatever is left of 100%, then all sizes are normalised to sum to 100. Attribute is `defaultsize`. */
2412
- defaultSize?: number;
2413
- /** Smallest size (percentage) the panel can be resized to. Attribute is `minsize`. */
2414
- minSize: number;
2415
- /** Largest size (percentage) the panel can be resized to. Attribute is `maxsize`. */
2416
- maxSize: number;
2417
- /** Current size as a percentage of the group (also written to the host's `flex-basis`); read-only — the parent group updates it on every resize. */
2418
- currentSize: number;
2419
- static styles: lit.CSSResult;
2420
- render(): lit_html.TemplateResult<1>;
2421
- }
2422
- declare global {
2423
- interface HTMLElementTagNameMap {
2424
- "rel-resizable-panel": RelResizablePanel;
2425
- }
2426
- }
2427
-
2428
- /**
2429
- * Focusable `role="separator"` splitter placed between two `<rel-resizable-panel>`s inside a `<rel-resizable-group>`; the group owns the drag and keyboard logic, the handle only renders the grab bar and exposes the split via ARIA.
2430
- */
2431
- declare class RelResizableHandle extends LitElement {
2432
- /** Axis the handle separates: `horizontal` = 8px-wide vertical bar with `col-resize` cursor, `vertical` = 8px-tall bar with `row-resize`; set automatically from the parent group's `direction` on first render and mirrored to `aria-orientation`. */
2433
- orientation: "horizontal" | "vertical";
2434
- /** @internal Index of this handle in the parent group; assigned by the group on first render. */
2435
- handleIndex: number;
2436
- /** Current split of the two neighbouring panels as a percentage 0–100 (share held by the leading panel); read-only — the parent group updates it and mirrors it to `aria-valuenow`. */
2437
- splitValue: number;
2438
- static styles: lit.CSSResult;
2439
- connectedCallback(): void;
2440
- updated(changed: Map<string, unknown>): void;
2441
- private updateAriaOrientation;
2442
- /** @internal Called by the parent group after every resize to store `splitValue` and write `aria-valuenow` / `aria-valuemin` / `aria-valuemax`. */
2443
- updateAriaValue(value: number): void;
2444
- render(): lit_html.TemplateResult<1>;
2445
- }
2446
- declare global {
2447
- interface HTMLElementTagNameMap {
2448
- "rel-resizable-handle": RelResizableHandle;
2449
- }
2450
- }
2451
-
2452
- type SegmentSize = "small" | "medium" | "large";
2453
- type SegmentAlign = "left" | "middle" | "right";
2454
- interface SegmentOption {
2455
- label: string;
2456
- value: string;
2457
- disabled?: boolean;
2458
- /** `rel-icon` name rendered before the label (Figma "Left Icon"), sized 16 / 18 / 20 with the control. */
2459
- icon?: string;
2460
- /**
2461
- * Trailing badge (Figma "Show Badge" / "Badge"), rendered after the label
2462
- * inside the segment's own 8px gap. A number or string shows the count at
2463
- * `medium` and `large`; at `small` Figma switches to the 6px dot, so the
2464
- * value is not displayed there. Use `true` for a dot at any size.
2465
- */
2466
- badge?: number | string | boolean;
2467
- }
2468
- /**
2469
- * Single-select segmented button group: renders one `<button role="radio">` per entry in `options` on a 2px-inset track (`role="radiogroup"`), with exactly one segment active at a time.
2470
- * @csspart base - The `role="radiogroup"` track that lays out the segments.
2471
- * @csspart option - Each segment's native `<button role="radio">`; the active one carries the `active` class and `aria-checked="true"`.
2472
- * @csspart icon - A segment's optional leading `rel-icon` element.
2473
- * @csspart badge - A segment's optional trailing `rel-badge`; rendered only when the option sets `badge`.
2474
- * @csspart label - The wrapper around a segment's label text.
2475
- */
2476
- declare class RelSegmentedButton extends LitElement {
2477
- /** The segments in order, each `{ label: string; value: string; disabled?: boolean; icon?: string; badge?: number | string | boolean }`. Set as a property (or a JSON array attribute); a disabled entry is rendered but cannot be selected, and `icon` names a `rel-icon` drawn before the label. */
2478
- options: SegmentOption[];
2479
- /** `value` of the currently selected segment. Updated by the component on click / arrow keys; set it to pre-select a segment (an unknown value selects nothing). */
2480
- active: string;
2481
- /** Control height, matching the TextField ramp: `small` (32px — 28px segments, `action-md` 12px text, 16px icons), `medium` (default, 40px — 36px segments, `action-lg` 14px text, 18px icons) or `large` (48px — 44px segments, `action-xl` 16px text, 20px icons). */
2482
- size: SegmentSize;
2483
- /** `justify-content` of the group's inline-flex container: `left` (default), `middle` or `right`. Only visible when the host is made wider than its content. */
2484
- align: SegmentAlign;
2485
- /** Accessible name for the radiogroup (it renders no visible group label). */
2486
- fieldAriaLabel?: string;
2487
- static styles: lit.CSSResult;
2488
- private setActive;
2489
- /** APG radio-group navigation: arrows move focus AND select, the group is
2490
- * one Tab stop (roving tabindex on the selected segment). */
2491
- private onKeyDown;
2492
- private renderSegment;
2493
- /**
2494
- * Figma's Show Badge axis: a 20x20 numeric badge at medium and large, and a
2495
- * 6px dot at small — where the count is not shown at all, so a numeric value
2496
- * degrades to a dot rather than being squeezed in. `badge: true` asks for the
2497
- * dot at any size.
2498
- */
2499
- private renderBadge;
2500
- render(): lit_html.TemplateResult<1>;
2501
- }
2502
-
2503
- type SelectState = "default" | "error" | "disabled";
2504
- type SelectSize = "small" | "medium" | "large";
2505
- type SelectBorder = "rounded" | "sharp";
2506
- /** Alias of the shared field type — kept so existing imports keep resolving. */
2507
- type SelectLabelStyle = FieldLabelStyle;
2508
- interface SelectOptionObject {
2509
- value: string;
2510
- label?: string;
2511
- supportingText?: string;
2512
- /** rel-icon name rendered as a prefix (leading icon) on the menu item. */
2513
- icon?: string;
2514
- }
2515
- type SelectOption = string | SelectOptionObject;
2516
- /**
2517
- * `<rel-select>` — combobox-style dropdown (Figma "Dropdown"): single- or multi-select
2518
- * with an optional in-panel search field and width-adaptive selection chips.
2519
- * @csspart label - The visible label above the trigger (default label style)
2520
- * @csspart trigger - The button that opens the panel; styled like a text field
2521
- * @csspart value - The value area inside the trigger (selected label, placeholder or chips)
2522
- * @csspart helper - The supporting / error text below the trigger
2523
- * @csspart panel - The dropdown listbox container (same node as `list`)
2524
- * @csspart list - Alias of `panel`
2525
- * @csspart input - The search input inside the panel
2526
- * @csspart option - A single-select option row
2527
- * @csspart option-label - A single-select row's first line (the option label)
2528
- * @csspart option-supporting-text - A single-select row's optional second line; rendered only when the option supplies `supportingText`
2529
- * @cssprop --rel-field-label-reserve - Height held for the label row whenever no label renders above the trigger — `labelStyle="none"`, and `labelStyle="floating"` where the label sits inside the trigger — so the trigger still lines up with labelled siblings in a form row. Defaults to that size's label line-height plus the wrapper gap (24px at `small`, 26px at `medium` and `large`). Set to `0` to collapse the row entirely (toolbars, table cells).
2530
- * @cssprop --rel-select-bg - Background of the trigger, the panel and its sticky header. No longer paints behind the floating label — see `--rel-select-surface`.
2531
- * @cssprop --rel-select-bg-disabled - Trigger background when `state="disabled"`
2532
- * @cssprop --rel-select-surface - Colour of the strip the floating label lays over the trigger's top border and focus ring, so the label appears to sit in a notch. It overshoots the band by 2px on the page side, so it must match the surface the field sits on. Defaults to `--rel-select-bg`. Set it when the field sits on a surface that is not the trigger's own fill, otherwise the strip shows as a short dash of the wrong colour. Mirrors `--rel-textfield-surface`.
2533
- * @cssprop --rel-select-surface-disabled - The same strip while `state="disabled"`, where the trigger fill changes. Defaults to `--rel-select-bg-disabled`.
2534
- * @cssprop --rel-select-border-default - Trigger border colour at rest
2535
- * @cssprop --rel-select-border-disabled - Trigger border colour when `state="disabled"`
2536
- * @cssprop --rel-select-border-focus - Trigger border colour on hover, while focused (where it also thickens to 2px) and while the panel is open
2537
- * @cssprop --rel-select-border-error - Trigger border colour when `state="error"`
2538
- * @cssprop --rel-select-radius - Trigger corner radius when `border="rounded"`
2539
- * @cssprop --rel-select-icon-color - Colour of the trigger icons, the search icon and option prefix icons
2540
- * @cssprop --rel-select-label-color - Colour of the static label and of the floated label
2541
- * @cssprop --rel-select-value-color - Colour of the selected value, the search text, option labels and the check mark
2542
- * @cssprop --rel-select-placeholder-color - Placeholder colour (trigger, resting floating label, search field, empty-list message)
2543
- * @cssprop --rel-select-placeholder-disabled-color - Placeholder colour when disabled
2544
- * @cssprop --rel-select-supporting-color - Supporting text colour (non-error)
2545
- * @cssprop --rel-select-menu-border - Panel border colour. The panel is borderless by design and only draws this on dark surfaces, where its shadow alone can't separate it from the page.
2546
- * @cssprop --rel-select-menu-radius - Panel corner radius
2547
- * @cssprop --rel-select-menu-shadow - Panel box-shadow
2548
- * @cssprop --rel-select-search-border - Border colour of the search field inside the panel
2549
- * @cssprop --rel-select-divider - Hairline under the search field, the Select-all row and every option row
2550
- * @cssprop --rel-select-option-hover - Background of the hovered / keyboard-highlighted / selected option and Select-all row
2551
- * @cssprop --rel-select-option-pressed - Background of an option row while it is being pressed
2552
- * @cssprop --rel-overlay-fallback-z - z-index of the panel on the NO-POPOVER-API fallback path only. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with rel-select, rel-autocomplete and the other pickers so the whole overlay family can be moved from one place; default 1001.
2553
- */
2554
- declare class RelSelect extends LitElement {
2555
- /** Visible label. Rendered above the trigger (`labelStyle="default"`) or floating inside it; omitted under `labelStyle="none"`. */
2556
- label: string;
2557
- /**
2558
- * Label presentation: `default` static label above the trigger; `floating` label
2559
- * rests in the value area and floats onto the border when focused, open or filled;
2560
- * `none` renders no label (supply `fieldAriaLabel` / `fieldAriaLabelledby`).
2561
- */
2562
- labelStyle: SelectLabelStyle;
2563
- /**
2564
- * Accessible name for the trigger. Required when `labelStyle="none"`, since no
2565
- * `<label>` renders and a placeholder is not a valid naming mechanism.
2566
- */
2567
- fieldAriaLabel?: string;
2568
- /** IDREF list of external element(s) naming the trigger. Wins over `fieldAriaLabel`. */
2569
- fieldAriaLabelledby?: string;
2570
- /**
2571
- * Options to list. Each entry is a plain string (used as both value and label) or
2572
- * `{ value, label?, supportingText?, icon? }`; `icon` is a `rel-icon` name rendered
2573
- * before the label, `supportingText` renders as a second line in both single- and multi-select rows.
2574
- *
2575
- * NOTE — the panel's search matches the LABEL ONLY. `supportingText` is displayed but not
2576
- * searched, so an option is not findable by its second line here. This differs deliberately from
2577
- * `rel-autocomplete`, whose filter matches the supporting line as well: there the second line
2578
- * carries identifying data (a DOT or MC number) that a user types to find the row, whereas a
2579
- * select's is descriptive ("Read-only", "Can edit, not delete") and matching it would return rows
2580
- * whose visible label has nothing to do with the query.
2567
+ * Options to list. Each entry is a plain string (used as both value and label) or
2568
+ * `{ value, label?, supportingText?, icon? }`; `icon` is a `rel-icon` name rendered
2569
+ * before the label, `supportingText` renders as a second line in both single- and multi-select rows.
2570
+ *
2571
+ * NOTE — the panel's search matches the LABEL ONLY. `supportingText` is displayed but not
2572
+ * searched, so an option is not findable by its second line here. This differs deliberately from
2573
+ * `rel-autocomplete`, whose filter matches the supporting line as well: there the second line
2574
+ * carries identifying data (a DOT or MC number) that a user types to find the row, whereas a
2575
+ * select's is descriptive ("Read-only", "Can edit, not delete") and matching it would return rows
2576
+ * whose visible label has nothing to do with the query.
2581
2577
  */
2582
2578
  options: SelectOption[];
2583
2579
  /** Enables multi-select: selected values render as removable chips, options as checkboxes, and a Select-all row appears in the panel. */
@@ -2621,17 +2617,7 @@ declare class RelSelect extends LitElement {
2621
2617
  * one by one instead of wrapping to a second line.
2622
2618
  */
2623
2619
  maxVisibleChips: number;
2624
- /** Hides the in-panel search field. Figma's "Single Option" dropdown has no search bar at all — use this for short, non-searchable lists. */
2625
- /**
2626
- * DELIBERATE DIVERGENCE — do not default this to `!multiple`. Figma's
2627
- * `.Menu Dropdown` shows the search row only on `Type=Multioption`; its
2628
- * `Type=Single Option` panels contain five Select Option items and nothing
2629
- * else. Here the search row renders for single-select too unless this is set.
2630
- * Ruled by the user: our behaviour stands and the DESIGN FILE follows our
2631
- * docs. A long single-select list is unusable without search, and making the
2632
- * default depend on `multiple` would change the panel of every existing
2633
- * single-select.
2634
- */
2620
+ /** Hides the in-panel search field. The search row shows in single- and multi-select panels alike unless this is set; use it for short lists that don't need searching (Figma's "Single Option" dropdown has no search bar). */
2635
2621
  noSearch: boolean;
2636
2622
  private open;
2637
2623
  private highlightedIndex;
@@ -2697,7 +2683,12 @@ declare class RelSelect extends LitElement {
2697
2683
  private addGlobalListeners;
2698
2684
  private removeGlobalListeners;
2699
2685
  private onOutsidePointerDown;
2700
- private onPageScroll;
2686
+ /** Outside scroll: reposition while the trigger is still visible, close
2687
+ * once it has scrolled out of view or under a sticky header; scrolls of
2688
+ * the list itself and of unrelated elements are ignored. See
2689
+ * utils/overlay-scroll.ts. The scroll close keeps focus on the trigger
2690
+ * without scrolling the page back to it. */
2691
+ private scrollWatch;
2701
2692
  /**
2702
2693
  * BOTH events fire, deliberately — do not delete either.
2703
2694
  *
@@ -2722,6 +2713,13 @@ declare class RelSelect extends LitElement {
2722
2713
  private toggleMultiOption;
2723
2714
  private toggleSelectAll;
2724
2715
  private removeChip;
2716
+ /**
2717
+ * A chip's remove button fired `removed`. When that button had keyboard
2718
+ * focus, the chip is about to disappear and focus would drop to <body>, so
2719
+ * hand it to the next chip's remove button, else the previous one, else the
2720
+ * trigger.
2721
+ */
2722
+ private onChipRemoved;
2725
2723
  private onTriggerClick;
2726
2724
  private onTriggerKeyDown;
2727
2725
  private onSearchInput;
@@ -2745,10 +2743,422 @@ declare class RelSelect extends LitElement {
2745
2743
  }
2746
2744
  declare global {
2747
2745
  interface HTMLElementTagNameMap {
2748
- "rel-select": RelSelect;
2746
+ "rel-select": RelSelect;
2747
+ }
2748
+ }
2749
+
2750
+ /** The layouts of Figma's Pagination "Type" axis. "Extended with Rows" is `extended` plus `show-rows-per-page`. */
2751
+ type PaginationType = "extended" | "compact" | "count-text" | "page-select";
2752
+ /**
2753
+ * Page navigation (Figma "Pagination"): prev/next ghost buttons with numbered pages, a compact "Page X of Y" readout, a "1–10 of 50" item-range readout, or a "Page [select] of Y" jump-to-page dropdown, optionally followed by a rows-per-page selector. Use it under tables and lists that load one page of results at a time; it never fetches data itself — listen for `rel-change` (and `rel-per-page-change`).
2754
+ * @csspart base - The `role="navigation"` root of the extended layout (page buttons plus the optional "Viewing page" label).
2755
+ * @csspart label - The `role="navigation"` root row of the count-text layout (item-range text plus prev/next nav).
2756
+ * @csspart page-select - The `rel-select` of the `page-select` layout (the jump-to-page dropdown). New in 1.5.0.
2757
+ * @csspart rows-per-page - The rows-per-page block (its text plus the `rel-select`), rendered with `show-rows-per-page`. New in 1.5.0.
2758
+ */
2759
+ declare class RelPagination extends RelElement {
2760
+ /** Figma "Type": `extended` = prev/next + numbered page buttons (auto-collapses to compact under 480px container width), `compact` = prev/next + "Page X of Y", `count-text` = "start–end of total" item range + prev/next around the current page number, `page-select` = prev/next around "Page [select] of Y", a dropdown that jumps to any page (new in 1.5.0). Every type except `page-select` has no jump-to-page control. */
2761
+ type: PaginationType;
2762
+ /** Controlled current page (1-based). When set, the element only emits `rel-change` and waits for the parent to update `page`; leave unset for uncontrolled behaviour. */
2763
+ page?: number;
2764
+ /** Starting page for uncontrolled use (ignored when `page` is set). */
2765
+ defaultPage: number;
2766
+ /** Total number of pages. `0` (the default) lets `totalItems` / `perPage` compute the page count instead. */
2767
+ totalPages: number;
2768
+ /** Total item count; with `perPage` it computes the page count when `totalPages` is `0` and is the "of N" figure in the count-text readout. */
2769
+ totalItems: number;
2770
+ /** Items per page, used to compute the page count from `totalItems` and the "start–end" range in the count-text readout. */
2771
+ perPage: number;
2772
+ /** Shows a "Viewing page X of Y" caption under the extended layout (ignored by the other types). Off by default. */
2773
+ showLabel: boolean;
2774
+ /** Dims the whole control to 40% opacity, shows a `not-allowed` cursor and ignores every page change. */
2775
+ disabled: boolean;
2776
+ /** Reserved for link-mode pagination. Currently accepted but not rendered: every page control is a `<button>`, so navigate in your `rel-change` handler instead. */
2777
+ hrefTemplate: string;
2778
+ /** Accessible name of the `role="navigation"` landmark (attribute `ariaLabel`). Set it when a page contains more than one pagination control. */
2779
+ ariaLabel: string;
2780
+ /** New in 1.5.0. Adds a rows-per-page selector after the navigation, with any `type` (Figma "Extended with Rows" is `type="extended"` with this set). Choosing a value sets `perPage`, fires `rel-per-page-change` and returns to page 1. Off by default: no rows-per-page selector. */
2781
+ showRowsPerPage: boolean;
2782
+ /** New in 1.5.0. The values the rows-per-page selector offers (a number array, or a JSON array in the attribute). The current `perPage` is added to the list when it is not in it. */
2783
+ rowsPerPageOptions: number[];
2784
+ /** New in 1.5.0. Text before the rows-per-page selector, also its accessible name. */
2785
+ rowsPerPageLabel: string;
2786
+ private currentPage;
2787
+ static styles: lit.CSSResult;
2788
+ connectedCallback(): void;
2789
+ updated(changedProperties: Map<string, unknown>): void;
2790
+ private get activePage();
2791
+ private get computedTotalPages();
2792
+ private generatePageNumbers;
2793
+ /** Abbreviate large page numbers (368178 → "368k") so buttons stay compact.
2794
+ Exact below 10,000; floored to k up to 1M; then M with one decimal. */
2795
+ private formatPageLabel;
2796
+ private handlePageChange;
2797
+ private renderChevronLeft;
2798
+ private renderChevronRight;
2799
+ private renderNavBtn;
2800
+ private renderExtended;
2801
+ private renderCompact;
2802
+ private renderCountText;
2803
+ /** Pages offered by the page-select dropdown: every page, or for very long
2804
+ ranges a window around the current page plus the first and last. */
2805
+ private pageOptions;
2806
+ /** Both dropdowns' panels may be wider than their 72px trigger: the longest
2807
+ value (`largest`), the row's 12px side padding, the 16px gap, the 18px
2808
+ check and 20px kept for a classic scrollbar (the page list scrolls past
2809
+ 9 pages). */
2810
+ private panelMinWidth;
2811
+ private stopInner;
2812
+ private onPageSelect;
2813
+ private onRowsPerPage;
2814
+ private renderPageSelect;
2815
+ private renderRowsPerPage;
2816
+ private renderLayout;
2817
+ render(): lit_html.TemplateResult<1>;
2818
+ }
2819
+
2820
+ type SideType = "top" | "right" | "bottom" | "left";
2821
+ type AlignType = "start" | "center" | "end";
2822
+ /**
2823
+ * Click-to-open floating panel anchored to a trigger (Figma "Popover"): the first child is the trigger, the `content` slot is the panel, which is promoted to the browser top layer and positioned in viewport coordinates.
2824
+ * @slot - The trigger: the first slotted element toggles the panel on pointer down / Enter / Space and receives `aria-haspopup` (from `has-popup`), `aria-expanded` and `aria-controls` (a bare `<div>` / `<span>` also gets `tabindex="0"` and `role="button"`).
2825
+ * @slot content - The panel body, usually a `<rel-popover-content>`; rendered inside the fixed, top-layer wrapper only while open.
2826
+ * @csspart arrow - The 14×14 chevron box rendered on the panel edge facing the trigger when `arrow` is set (absent otherwise).
2827
+ * @cssprop --rel-popover-arrow-bg - Fill of the `arrow` chevron (default `--rel-bg-neutral`, matching `<rel-popover-content>`).
2828
+ * @cssprop --rel-popover-arrow-border - **Deprecated.** Stroke of the `arrow` chevron's two slanted edges. The panel no longer draws a border — overlapping surfaces are separated by shadow — so this defaults to `transparent` and the chevron has no edge. Set it to restore a stroke.
2829
+ */
2830
+ declare class RelPopover extends RelElement {
2831
+ /** Controlled open state. Leave unset for uncontrolled mode; once set (true or false) the component only emits `rel-open-change` and you must update `open` yourself. */
2832
+ open?: boolean;
2833
+ /** Initial state in uncontrolled mode (attribute `default-open`); ignored when `open` is set. */
2834
+ defaultOpen: boolean;
2835
+ /** Blocks trigger interaction and dims the host to 60% opacity; does not close an already-open panel. */
2836
+ disabled: boolean;
2837
+ /** Which side of the trigger the panel opens on: `top`, `right`, `bottom` (default) or `left`. The panel is clamped 8px inside the viewport but never flips to another side. */
2838
+ side: SideType;
2839
+ /** Alignment along the cross axis: `start` (left / top edge of the trigger), `center` (default) or `end` (right / bottom edge). */
2840
+ align: AlignType;
2841
+ /** Gap between trigger and panel in pixels (attribute `side-offset`, default 8). With `arrow` the effective gap is at least 13px. */
2842
+ sideOffset: number;
2843
+ /** Extra shift along the alignment axis in pixels (attribute `align-offset`, default 0); positive moves right / down. */
2844
+ alignOffset: number;
2845
+ /** Keep Tab / Shift+Tab cycling inside the panel while open (attribute `trap-focus`); use for forms. */
2846
+ trapFocus: boolean;
2847
+ /** Close on Escape (attribute `close-on-escape`, default true; switch off with `close-on-escape="false"` or `el.closeOnEscape = false`). */
2848
+ closeOnEscape: boolean;
2849
+ /** Close on a pointer-down outside the trigger and panel (attribute `close-on-outside-click`, default true; switch off with `close-on-outside-click="false"` or the property). Also lets another popover's opening dismiss this one. */
2850
+ closeOnOutsideClick: boolean;
2851
+ /**
2852
+ * Opt-in pointer (audit 23a): a 14×11 triangle on the panel edge that faces
2853
+ * the trigger — the Figma Popover "Chevron" — following `side` and tracking
2854
+ * the trigger's centre along the cross axis (clamped inside the panel).
2855
+ * It is drawn with the panel's tokens (`--rel-popover-arrow-bg` /
2856
+ * `--rel-popover-arrow-border`, defaulting to rel-popover-content's
2857
+ * background and border). With the arrow on, the panel keeps at least the
2858
+ * arrow's height plus 2px away from the trigger so the tip never overlaps
2859
+ * it; larger `sideOffset` values still apply. Default off — existing
2860
+ * popovers render exactly as before.
2861
+ */
2862
+ arrow: boolean;
2863
+ /** New in 1.5.0. Value written to the trigger's `aria-haspopup` (attribute `has-popup`): `dialog` (default) for a panel of content, or `menu`, `listbox`, `tree`, `grid` or `true` when the panel holds that widget. A panel holding a `rel-menu` takes `menu`. */
2864
+ hasPopup: "dialog" | "menu" | "listbox" | "tree" | "grid" | "true";
2865
+ /** Figma Popover (6338:563) chevron: 14 wide × 11 tall. */
2866
+ private static readonly ARROW_WIDTH;
2867
+ private static readonly ARROW_HEIGHT;
2868
+ private internalOpen;
2869
+ private contentStyle;
2870
+ private contentWrapper;
2871
+ private contentSlot?;
2872
+ private triggerEl;
2873
+ private wasOpen;
2874
+ /** The last interaction with this popover (trigger, panel or an outside
2875
+ * dismiss) was a pointer press, not a key. Decides whether the focus moves
2876
+ * on open and close may draw a focus ring (see focusOptions). */
2877
+ private pointerInteraction;
2878
+ private uniqueId;
2879
+ static styles: lit.CSSResult;
2880
+ connectedCallback(): void;
2881
+ disconnectedCallback(): void;
2882
+ firstUpdated(): void;
2883
+ updated(changed: Map<string, unknown>): void;
2884
+ private isControlled;
2885
+ private get activeOpen();
2886
+ private setOpen;
2887
+ private onHostPointerDown;
2888
+ private onHostKeyDown;
2889
+ private onOpen;
2890
+ private onClose;
2891
+ /**
2892
+ * Light-dismiss every other open popover that isn't an ancestor of this
2893
+ * one (nested popovers keep their parent open). This is what the outside-
2894
+ * pointerdown listener would do for a click on our trigger — except the
2895
+ * trigger handler stops that pointerdown in the capture phase, and a
2896
+ * keyboard (Enter/Space) or programmatic open never produces one at all.
2897
+ * Honors each popover's own `closeOnOutsideClick` setting.
2898
+ */
2899
+ private closeOtherPopovers;
2900
+ private showNativePopover;
2901
+ private hideNativePopover;
2902
+ private addGlobalListeners;
2903
+ private removeGlobalListeners;
2904
+ private onOutsidePointerDown;
2905
+ private onKeyDown;
2906
+ /** The panel content is SLOTTED light DOM, often nested inside other
2907
+ * components' shadow roots (a rel-button in rel-popover-content), so the
2908
+ * search walks the composed tree — a querySelectorAll on the wrapper never
2909
+ * saw it, and focus never entered the panel. With nothing focusable, the
2910
+ * panel itself takes focus so it is announced: a rel-popover-content
2911
+ * focuses its named role="dialog" box; any other element falls back to the
2912
+ * wrapper (tabindex="-1"). */
2913
+ private focusFirst;
2914
+ /** After a pointer press the trigger never takes focus (onHostPointerDown
2915
+ * prevents it), so Chromium counts our scripted focus as :focus-visible and
2916
+ * a mouse open drew a focus ring in the panel (and on the trigger after a
2917
+ * mouse close). `focusVisible: false` keeps those moves ring-free; a key
2918
+ * press inside the panel still shows the focused control's own ring (a
2919
+ * rel-menu row: its data-keyboard rule). Keyboard and scripted opens pass
2920
+ * nothing, so the browser decides as before. A browser that doesn't know
2921
+ * the `focusVisible` option ignores the unknown dictionary member and
2922
+ * behaves exactly as before this change. */
2923
+ private focusOptions;
2924
+ /** Focus returns to the trigger's own control (APG: back to the invoking
2925
+ * element) — a rel-button host is not focusable, its inner <button> is.
2926
+ * Only when focus is in the popover or was dropped to <body> (the panel
2927
+ * hiding blurs it): a close caused by focus moving elsewhere, or a
2928
+ * programmatic close, must not pull focus away from where the user is. */
2929
+ private restoreFocus;
2930
+ private triggerControl;
2931
+ /** Whether the deep active element is inside the panel (the wrapper or the
2932
+ * slotted content) or anywhere in the popover (the trigger included),
2933
+ * walking the composed tree. */
2934
+ private focusIn;
2935
+ private trapFocusLoop;
2936
+ private updatePosition;
2937
+ private onTriggerSlotChange;
2938
+ private applyTriggerAria;
2939
+ render(): lit_html.TemplateResult<1>;
2940
+ }
2941
+ declare global {
2942
+ interface HTMLElementTagNameMap {
2943
+ "rel-popover": RelPopover;
2944
+ }
2945
+ }
2946
+
2947
+ /** Figma Size axis, spelled out. */
2948
+ type PopoverContentSize = "small" | "medium" | "large";
2949
+ /** Alternative spellings, not deprecations — the short names keep working. */
2950
+ type PopoverContentSizeAlias = "sm" | "md" | "lg";
2951
+ type PopoverContentSizeInput = PopoverContentSize | PopoverContentSizeAlias;
2952
+ /**
2953
+ * Styled panel body for `<rel-popover>` (Figma "Popover" surface): neutral background, no border, 8px radius, shadow/lg, max-width 400px, with heading / paragraph typography for slotted content. Place it in the popover's `content` slot; any other element works there too.
2954
+ * @slot - The panel content (headings, paragraphs, form controls, buttons).
2955
+ * @csspart content - The padded `role="dialog"` box that carries the surface, shadow and size padding.
2956
+ */
2957
+ declare class RelPopoverContent extends RelElement {
2958
+ /** Accessible name applied to the inner `role="dialog"` box (attribute `aria-label`); describe what the panel is for. */
2959
+ ariaLabel: string;
2960
+ /** Figma Size axis: panel padding 12px (`small`), 16px (`medium`, default) or 20px (`large`). `sm` / `md` / `lg` are alternative spellings and stay supported — they are normalised to the full words, not deprecated. */
2961
+ size: PopoverContentSizeInput;
2962
+ /** Alternative spellings, NOT deprecations — no console notice. Normalising
2963
+ * keeps the reflected attribute canonical, so the :host([size=…]) rules only
2964
+ * need the full words. */
2965
+ protected willUpdate(changed: Map<string, unknown>): void;
2966
+ static styles: lit.CSSResult;
2967
+ /** Focuses the `role="dialog"` box rather than the host (which is not
2968
+ * focusable). rel-popover lands focus here when the panel has nothing
2969
+ * focusable, so a screen reader announces the dialog by its `aria-label`. */
2970
+ focus(options?: FocusOptions): void;
2971
+ render(): lit_html.TemplateResult<1>;
2972
+ }
2973
+
2974
+ type ProgressMode = "linear-determinate" | "linear-indeterminate" | "circular-determinate" | "circular-indeterminate";
2975
+ /**
2976
+ * Progress indicator (Figma "Progress"): a linear bar or a circular ring, each either value-driven (determinate) or looping (indeterminate).
2977
+ * @csspart track - The linear track `<div>` that carries `role="progressbar"` (linear modes only)
2978
+ * @csspart indicator - The filled / sliding bar inside the linear track (linear modes only)
2979
+ */
2980
+ declare class RelProgress extends RelElement {
2981
+ /** Shape and behaviour: `linear-*` is a full-width 2px bar, `circular-*` a 36px ring; `*-determinate` fills to `value`, `*-indeterminate` loops an animation and ignores `value`. */
2982
+ mode: ProgressMode;
2983
+ /** Completion percentage 0–100 for the determinate modes; out-of-range values are clamped before rendering and before `aria-valuenow`. */
2984
+ value: number;
2985
+ /** Accessible name of the progressbar (not rendered visually); defaults to "Progress" for determinate and "Loading" for indeterminate modes. */
2986
+ label: string;
2987
+ private radius;
2988
+ private circumference;
2989
+ static styles: lit.CSSResult;
2990
+ private get safeValue();
2991
+ private get accessibleName();
2992
+ private renderLinearDeterminate;
2993
+ private renderLinearIndeterminate;
2994
+ private renderCircularDeterminate;
2995
+ private renderCircularIndeterminate;
2996
+ render(): lit_html.TemplateResult<1>;
2997
+ }
2998
+
2999
+ /**
3000
+ * Flex container that lays out direct-child `<rel-resizable-panel>`s along one axis and lets the user drag or keyboard-nudge the `<rel-resizable-handle>` between two neighbours to trade percentage width/height between them.
3001
+ * @slot - The panels and handles, as direct children in display order (panel, handle, panel, …)
3002
+ * @fires rel-resize - Fired continuously while a handle is dragged. Detail: `{ direction, sizes: number[] }` with one percentage per panel.
3003
+ * @fires rel-resize-end - Fired once when a drag ends (pointerup) and after every keyboard resize step. Detail: `{ direction, sizes: number[] }` with one percentage per panel.
3004
+ */
3005
+ declare class RelResizableGroup extends RelElement {
3006
+ /** Layout axis: `horizontal` places panels side by side and resizes widths, `vertical` stacks them and resizes heights; also pushed to every child handle's `orientation`. */
3007
+ direction: "horizontal" | "vertical";
3008
+ private panels;
3009
+ private handles;
3010
+ private isDragging;
3011
+ private activeHandleIndex;
3012
+ private startPointerPos;
3013
+ private startSizes;
3014
+ static styles: lit.CSSResult;
3015
+ connectedCallback(): void;
3016
+ disconnectedCallback(): void;
3017
+ firstUpdated(): void;
3018
+ private initializeLayout;
3019
+ private calculateInitialSizes;
3020
+ private onPointerDown;
3021
+ private startDrag;
3022
+ private onPointerMove;
3023
+ private onPointerUp;
3024
+ private applyDragResize;
3025
+ private onKeyDown;
3026
+ private applyKeyboardResize;
3027
+ private applySizes;
3028
+ private dispatchResizeEvent;
3029
+ private addGlobalListeners;
3030
+ private removeGlobalListeners;
3031
+ render(): lit_html.TemplateResult<1>;
3032
+ }
3033
+ declare global {
3034
+ interface HTMLElementTagNameMap {
3035
+ "rel-resizable-group": RelResizableGroup;
3036
+ }
3037
+ }
3038
+
3039
+ /**
3040
+ * One resizable region inside a `<rel-resizable-group>`: a padded, scrollable (`overflow: auto`) flex column whose size along the group axis is a percentage managed by the group. While its content overflows, the panel becomes a keyboard-focusable named region so it can be scrolled without a mouse.
3041
+ * @slot - The panel's content; slotted `h1`–`h4` get the headings-md role
3042
+ */
3043
+ declare class RelResizablePanel extends RelElement {
3044
+ /** Initial size as a percentage of the group; panels without one share whatever is left of 100%, then all sizes are normalised to sum to 100. */
3045
+ defaultSize?: number;
3046
+ /** Smallest size (percentage) the panel can be resized to. */
3047
+ minSize: number;
3048
+ /** Largest size (percentage) the panel can be resized to. */
3049
+ maxSize: number;
3050
+ /** New in 1.5.0. Accessible name the panel carries while its content overflows (it is then a focusable `role="region"`). Defaults to "Panel N", N being its position among the group's panels. Ignored when the page sets its own `aria-label` / `aria-labelledby`. */
3051
+ panelLabel?: string;
3052
+ /** Current size as a percentage of the group (also written to the host's `flex-basis`); read-only — the parent group updates it on every resize. */
3053
+ currentSize: number;
3054
+ /** True while the content overflows the panel in either axis; read-only, kept current by a ResizeObserver. */
3055
+ get scrollable(): boolean;
3056
+ private overflowing;
3057
+ private resizeObserver?;
3058
+ private mutationObserver?;
3059
+ private observedChildren;
3060
+ private frame;
3061
+ /** The page set `tabindex` itself: never touch it, nor role / name. */
3062
+ private pageOwnsTabindex;
3063
+ /** The tabindex value this panel last wrote, or null when it wrote none. */
3064
+ private writtenTabindex;
3065
+ private ownsRole;
3066
+ private ownsLabel;
3067
+ static styles: lit.CSSResult;
3068
+ connectedCallback(): void;
3069
+ disconnectedCallback(): void;
3070
+ protected updated(changed: PropertyValues<this>): void;
3071
+ private onSlotChange;
3072
+ private onFocusOut;
3073
+ /** Measures one frame after the change: no layout in the observer callback, no ResizeObserver loop errors. */
3074
+ private scheduleMeasure;
3075
+ private defaultLabel;
3076
+ private applyScrollable;
3077
+ render(): lit_html.TemplateResult<1>;
3078
+ }
3079
+ declare global {
3080
+ interface HTMLElementTagNameMap {
3081
+ "rel-resizable-panel": RelResizablePanel;
3082
+ }
3083
+ }
3084
+
3085
+ /**
3086
+ * Focusable `role="separator"` splitter placed between two `<rel-resizable-panel>`s inside a `<rel-resizable-group>`; the group owns the drag and keyboard logic, the handle only renders the grab bar and exposes the split via ARIA.
3087
+ */
3088
+ declare class RelResizableHandle extends RelElement {
3089
+ /** Axis the handle separates: `horizontal` = 8px-wide vertical bar with `col-resize` cursor, `vertical` = 8px-tall bar with `row-resize`; set automatically from the parent group's `direction` on first render and mirrored to `aria-orientation`. */
3090
+ orientation: "horizontal" | "vertical";
3091
+ /** @internal Index of this handle in the parent group; assigned by the group on first render. */
3092
+ handleIndex: number;
3093
+ /** Current split of the two neighbouring panels as a percentage 0–100 (share held by the leading panel); read-only — the parent group updates it and mirrors it to `aria-valuenow`. */
3094
+ splitValue: number;
3095
+ static styles: lit.CSSResult;
3096
+ connectedCallback(): void;
3097
+ updated(changed: Map<string, unknown>): void;
3098
+ private updateAriaOrientation;
3099
+ /** @internal Called by the parent group after every resize to store `splitValue` and write `aria-valuenow` / `aria-valuemin` / `aria-valuemax`. */
3100
+ updateAriaValue(value: number): void;
3101
+ render(): lit_html.TemplateResult<1>;
3102
+ }
3103
+ declare global {
3104
+ interface HTMLElementTagNameMap {
3105
+ "rel-resizable-handle": RelResizableHandle;
2749
3106
  }
2750
3107
  }
2751
3108
 
3109
+ type SegmentSize = "small" | "medium" | "large";
3110
+ type SegmentAlign = "left" | "middle" | "right";
3111
+ interface SegmentOption {
3112
+ label: string;
3113
+ value: string;
3114
+ disabled?: boolean;
3115
+ /** `rel-icon` name rendered before the label (Figma "Left Icon"), sized 16 / 18 / 20 with the control. */
3116
+ icon?: string;
3117
+ /**
3118
+ * Trailing badge (Figma "Show Badge" / "Badge"), rendered after the label
3119
+ * inside the segment's own 8px gap. A number or string shows the count at
3120
+ * `medium` and `large`; at `small` Figma switches to the 6px dot, so the
3121
+ * value is not displayed there. Use `true` for a dot at any size.
3122
+ */
3123
+ badge?: number | string | boolean;
3124
+ }
3125
+ /**
3126
+ * Single-select segmented button group: renders one `<button role="radio">` per entry in `options` on a 2px-inset track (`role="radiogroup"`), with exactly one segment active at a time.
3127
+ * @csspart base - The `role="radiogroup"` track that lays out the segments.
3128
+ * @csspart option - Each segment's native `<button role="radio">`; the active one carries the `active` class and `aria-checked="true"`.
3129
+ * @csspart icon - A segment's optional leading `rel-icon` element.
3130
+ * @csspart badge - A segment's optional trailing `rel-badge`; rendered only when the option sets `badge`.
3131
+ * @csspart label - The wrapper around a segment's label text.
3132
+ */
3133
+ declare class RelSegmentedButton extends RelElement {
3134
+ /** The segments in order, each `{ label: string; value: string; disabled?: boolean; icon?: string; badge?: number | string | boolean }`. Set as a property (or a JSON array attribute); a disabled entry is rendered but cannot be selected, and `icon` names a `rel-icon` drawn before the label. */
3135
+ options: SegmentOption[];
3136
+ /** `value` of the currently selected segment. Updated by the component on click / arrow keys; set it to pre-select a segment (an unknown value selects nothing). */
3137
+ active: string;
3138
+ /** Disables the whole group (click and keyboard) without changing individual `options[].disabled` settings. Angular `control.disable()` uses it. New in 1.5.0. */
3139
+ disabled: boolean;
3140
+ /** Control height, matching the TextField ramp: `small` (32px — 28px segments, `action-md` 12px text, 16px icons), `medium` (default, 40px — 36px segments, `action-lg` 14px text, 18px icons) or `large` (48px — 44px segments, `action-xl` 16px text, 20px icons). */
3141
+ size: SegmentSize;
3142
+ /** `justify-content` of the group's inline-flex container: `left` (default), `middle` or `right`. Only visible when the host is made wider than its content. */
3143
+ align: SegmentAlign;
3144
+ /** Accessible name for the radiogroup (it renders no visible group label). */
3145
+ fieldAriaLabel?: string;
3146
+ static styles: lit.CSSResult;
3147
+ private setActive;
3148
+ /** APG radio-group navigation: arrows move focus AND select, the group is
3149
+ * one Tab stop (roving tabindex on the selected segment). */
3150
+ private onKeyDown;
3151
+ private renderSegment;
3152
+ /**
3153
+ * Figma's Show Badge axis: a 20x20 numeric badge at medium and large, and a
3154
+ * 6px dot at small — where the count is not shown at all, so a numeric value
3155
+ * degrades to a dot rather than being squeezed in. `badge: true` asks for the
3156
+ * dot at any size.
3157
+ */
3158
+ private renderBadge;
3159
+ render(): lit_html.TemplateResult<1>;
3160
+ }
3161
+
2752
3162
  type SkeletonShape = "line" | "circle" | "block";
2753
3163
  type SkeletonAnimation = "pulse" | "shimmer" | "none";
2754
3164
  /**
@@ -2758,7 +3168,7 @@ type SkeletonAnimation = "pulse" | "shimmer" | "none";
2758
3168
  * @cssprop --rel-skeleton-line-height - Height of each text bar when `shape="line"`; default 16px (Figma's Line shape).
2759
3169
  * @cssprop --rel-skeleton-highlight - Lighter band that sweeps across the placeholder when `animation="shimmer"` (defaults to the elevated-2 neutral background token).
2760
3170
  */
2761
- declare class RelSkeleton extends LitElement {
3171
+ declare class RelSkeleton extends RelElement {
2762
3172
  /** Placeholder shape: `block` fills the host (min 16px tall), `circle` is a square-aspect disc sized by the host width, `line` renders `lines` 16px text bars with the last one at 65% width. */
2763
3173
  shape: SkeletonShape;
2764
3174
  /** Number of text bars when `shape="line"` (minimum 1, --rel-spacing-xs apart); ignored by the other shapes. */
@@ -2813,7 +3223,7 @@ type ModalVariant = "default" | "text";
2813
3223
  * @cssprop --rel-modal-transition-duration - Open/close animation duration; default `0.3s`
2814
3224
  * @cssprop --rel-modal-transition-timing - Open/close animation easing; default `cubic-bezier(0.32, 0.72, 0, 1)`
2815
3225
  */
2816
- declare class RelModal extends LitElement {
3226
+ declare class RelModal extends RelElement {
2817
3227
  /** Whether the modal is shown. Toggle it to open/close; every change fires `rel-open-change`. */
2818
3228
  open: boolean;
2819
3229
  /** Where the panel is anchored: `bottom` (48px above the bottom edge, slides up), `top` (48px below the top edge, slides down) or `center` (fades and scales in). Below 480px with `breakpoint="responsive"` every side collapses to a full-width bottom sheet. */
@@ -2860,20 +3270,29 @@ declare class RelModal extends LitElement {
2860
3270
  private overlayElement;
2861
3271
  private panelElement;
2862
3272
  private previousFocus;
2863
- private originalBodyOverflow;
2864
- private originalBodyPaddingRight;
2865
3273
  static styles: lit.CSSResult;
2866
3274
  connectedCallback(): void;
3275
+ firstUpdated(): void;
2867
3276
  /**
2868
3277
  * The footer divider is conditional: the designer confirmed that short
2869
3278
  * content (a confirmation message) has no divider, but a body that scrolls
2870
3279
  * does — it is a scroll affordance, not decoration. There is no CSS way to
2871
3280
  * ask "does this element scroll", so it is measured and reflected onto the
2872
- * host as `body-scrolls`, re-measured whenever the body or its contents
2873
- * resize.
3281
+ * host as `body-scrolls`.
3282
+ *
3283
+ * What is observed matters. Once the panel reaches its max height (or is
3284
+ * fullscreen, where it is always fixed) `.content` stops resizing as its
3285
+ * content grows, and its only shadow child is the slot, which has no box.
3286
+ * So the observer also watches every element assigned to the default slot
3287
+ * (re-collected on slotchange): content added later, a child growing in
3288
+ * place, an image loading, all resize one of those. Window resizes reach
3289
+ * `.content` itself.
2874
3290
  */
2875
3291
  private bodyScrollObserver?;
2876
- private watchBodyScroll;
3292
+ private bodyMeasureQueued;
3293
+ private measureBodyScroll;
3294
+ private queueBodyMeasure;
3295
+ private observeBody;
2877
3296
  disconnectedCallback(): void;
2878
3297
  protected willUpdate(changed: Map<string, unknown>): void;
2879
3298
  updated(changed: Map<string, unknown>): void;
@@ -2892,6 +3311,206 @@ declare global {
2892
3311
  }
2893
3312
  }
2894
3313
 
3314
+ /** One point of a stroke: `x` and `y` in CSS pixels of the pad (see `strokes`), `t` in epoch milliseconds, and the pointer's `pressure` (0–1; 0.5 for a pressed mouse). */
3315
+ type SignaturePoint = [x: number, y: number, t: number, pressure: number];
3316
+ /** One pen-down to pen-up stroke of a `rel-signature-pad`. */
3317
+ interface SignatureStroke {
3318
+ points: SignaturePoint[];
3319
+ /** The `PointerEvent.pointerType` that drew it. Only `"pen"` strokes vary their width with pressure. */
3320
+ pointerType?: string;
3321
+ }
3322
+ /** How the signature is given: drawn with a pointer, or typed as a name. */
3323
+ type SignaturePadMode = "draw" | "type";
3324
+ /** Detail of the `rel-change` event of `rel-signature-pad`. */
3325
+ interface SignaturePadChangeDetail {
3326
+ value: string;
3327
+ strokes: SignatureStroke[];
3328
+ signedAt: string;
3329
+ mode: SignaturePadMode;
3330
+ }
3331
+ /**
3332
+ * Signature capture field. The user signs with a mouse, pen or finger on a 160px pad that fills its container's width; the field has a label, helper text, error and disabled states like `rel-textfield`, and Undo and Clear buttons in its helper row. Undo removes the last stroke and also reverses Clear. `value` is the signature as a trimmed PNG data URL; `strokes` holds the vector record and `toSVG()` exports it. A "Type your name instead" link switches to type mode, where the user types their name and it renders as the signature in Poppins italic: the keyboard alternative to drawing (WCAG 2.2 SC 2.5.7).
3333
+ *
3334
+ * New in 1.5.0.
3335
+ * @csspart label - The label above the pad.
3336
+ * @csspart mode-toggle - The link-style `rel-button` beside the label that switches between drawing ("Type your name instead") and typing ("Draw instead"). Hidden while `readonly`.
3337
+ * @csspart pad - The bordered drawing area. In draw mode it is a focusable `role="img"` named by the label and the signature's state; in type mode it holds the name input.
3338
+ * @csspart canvas - The `<canvas>` the ink is drawn on.
3339
+ * @csspart typed-input - The text input of type mode, drawn on the baseline in Poppins italic.
3340
+ * @csspart placeholder - The "Sign here" text ("Type your full name" in type mode), shown while the pad is empty.
3341
+ * @csspart baseline - The line near the bottom of the pad.
3342
+ * @csspart helper - The helper, error or "Signed …" text below the pad.
3343
+ * @csspart actions - The row that holds the Undo and Clear buttons.
3344
+ * @csspart undo - The Undo `rel-button` (ghost, small).
3345
+ * @csspart clear - The Clear `rel-button` (secondary, small).
3346
+ * @cssprop --rel-signature-pad-height - Height of the pad, border included (default 160px).
3347
+ * @fires rel-begin - The user put the pointer down to start a stroke. Detail: `{}`.
3348
+ * @fires rel-end - The user finished a stroke. Detail: `{}`.
3349
+ * @fires rel-change - The signature changed through the user: a finished stroke, Undo, Clear, a committed typed name (on blur or Enter) or a switch of mode. Detail: `{ value, strokes, signedAt, mode }`. Not fired for property changes or the `undo()` / `clear()` methods.
3350
+ */
3351
+ declare class RelSignaturePad extends RelElement {
3352
+ static styles: lit.CSSResult;
3353
+ /** Visible label above the pad. A `required` asterisk is appended. */
3354
+ label?: string;
3355
+ /** Marks the signature as required: the label gets an asterisk. */
3356
+ required: boolean;
3357
+ /** Helper line below the pad. Defaults to "Use a mouse, pen or finger to sign"; once signed, the line shows "Signed {date and time}" instead. */
3358
+ helperText?: string;
3359
+ /** Text in the helper line while disabled (default "Signing is unavailable"). */
3360
+ disabledText?: string;
3361
+ /** Puts the field in the error state: danger border, and `error-message` replacing the helper text. */
3362
+ error: boolean;
3363
+ /** Text shown below the pad, with an alert icon, instead of the helper text while `error` is set. */
3364
+ errorMessage?: string;
3365
+ /** Disables the field: no drawing, Undo and Clear disabled, the pad leaves the Tab order, and the helper line shows `disabled-text`. */
3366
+ disabled: boolean;
3367
+ /** Shows the signature and its timestamp without letting it change: a subtle border, no Undo or Clear buttons, no drawing. */
3368
+ readonly: boolean;
3369
+ /** Text in the empty pad (default "Sign here"). It hides on the first ink; the baseline stays. */
3370
+ placeholder: string;
3371
+ /** Name of the field in a form. */
3372
+ name?: string;
3373
+ /** Accessible name of the pad when there is no visible `label`. */
3374
+ fieldAriaLabel?: string;
3375
+ /**
3376
+ * The signature as a PNG data URL, trimmed to the ink at 2×, with black ink on a transparent background in every theme; `""` when empty. Set it to show a stored signature: it is drawn in the theme's ink colour and replaces the strokes, so Undo cannot take it apart stroke by stroke (Clear removes it, and Undo brings it back). Setting `""` empties the pad.
3377
+ */
3378
+ value: string;
3379
+ /**
3380
+ * The vector record of the signature, one entry per stroke: `{ points: [x, y, t, pressure][], pointerType? }`. Coordinates are CSS pixels of the pad when the first stroke was drawn (a narrower pad later shows the signature scaled down to fit, and the coordinates do not change); `t` is epoch milliseconds. Set it to redraw a stored signature that can be added to; that starts a new Undo history.
3381
+ */
3382
+ strokes: SignatureStroke[];
3383
+ /** `draw` (default) captures the signature with a pointer; `type` takes a typed name and renders it in Poppins italic. The user switches with the link beside the label. Each mode keeps its own content; only the active mode makes the `value`. */
3384
+ mode: SignaturePadMode;
3385
+ /** The name typed in type mode. The `value` in type mode is this name rendered as a PNG, black, in Poppins italic. */
3386
+ typedName: string;
3387
+ private signedAtIso;
3388
+ private typedAtIso;
3389
+ private typedHistory;
3390
+ private drawing;
3391
+ private history;
3392
+ private stored;
3393
+ private padEl?;
3394
+ private canvas?;
3395
+ private typedInput?;
3396
+ private readonly uid;
3397
+ /** The strokes and value this component last set itself; anything else was assigned from outside. */
3398
+ private ownStrokes;
3399
+ private ownValue;
3400
+ private ownTypedName;
3401
+ /** Size of the pad when the current signature began: the space `strokes` coordinates live in. */
3402
+ private ref?;
3403
+ /** Current CSS size of the canvas and its backing-store size in device pixels. */
3404
+ private size?;
3405
+ private pixels?;
3406
+ private current;
3407
+ private activePointer;
3408
+ private frame;
3409
+ private resizeObserver?;
3410
+ private themeObserver?;
3411
+ private mediaQueries;
3412
+ /** The stored signature recoloured to one ink colour, cached across frames. */
3413
+ private tint?;
3414
+ static formAssociated: boolean;
3415
+ private readonly internals;
3416
+ /** An outer `<label>` names the pad and focuses it; a `<fieldset disabled>` disables it. */
3417
+ private readonly outerLabel;
3418
+ /** `disabled`, or an ancestor `<fieldset disabled>`. */
3419
+ private get isDisabled();
3420
+ /** @internal Form-associated lifecycle: an ancestor `<fieldset disabled>` disables the pad too. */
3421
+ formDisabledCallback(): void;
3422
+ /** @internal Form-associated lifecycle: a form reset empties both modes and their Undo history, without `rel-change`. */
3423
+ formResetCallback(): void;
3424
+ /** @internal Form-associated lifecycle: the browser restores the signature (back / forward navigation, autofill) from the state `syncForm()` saved. */
3425
+ formStateRestoreCallback(state: string | File | FormData | null): void;
3426
+ /** Form value, restore state and validity. A read-only pad, like a read-only input, is not validated. */
3427
+ private syncForm;
3428
+ /** When the signature was started, as an ISO 8601 string; `""` while empty. Set by the first stroke and cleared by Clear. Read-only. */
3429
+ get signedAt(): string;
3430
+ /** True when the active mode has no signature: no strokes and no stored signature in draw mode, a blank name in type mode. */
3431
+ isEmpty(): boolean;
3432
+ /** Removes the last stroke, or restores what Clear removed (in type mode, the cleared name). Does not fire `rel-change`. */
3433
+ undo(): void;
3434
+ /** Removes the whole signature of the active mode; Undo brings it back. Does not fire `rel-change`. */
3435
+ clear(): void;
3436
+ /**
3437
+ * The signature as a data URL of the given type (default `"image/png"`), trimmed to the ink at 2× with black ink; `"image/svg+xml"` returns `toSVG()` as a data URL, and `"image/jpeg"` gets a white background. `""` when empty.
3438
+ */
3439
+ toDataURL(type?: string): string;
3440
+ /** The signature as an SVG document string, in black and trimmed. Draw mode gives smoothed paths in the `strokes` coordinate space (a stored signature from `value` is embedded as an image); type mode gives the name as `<text>` in Poppins italic. `""` when empty. */
3441
+ toSVG(): string;
3442
+ connectedCallback(): void;
3443
+ disconnectedCallback(): void;
3444
+ protected firstUpdated(): void;
3445
+ protected willUpdate(changed: PropertyValues<this>): void;
3446
+ protected updated(): void;
3447
+ private get typing();
3448
+ /** No strokes and no stored signature (draw mode's content, whatever the active mode). */
3449
+ private get drawEmpty();
3450
+ private get canUndo();
3451
+ private snapshot;
3452
+ private restore;
3453
+ private setStrokes;
3454
+ private setTypedName;
3455
+ /** Recomputes `value` from the active mode. */
3456
+ private commit;
3457
+ /** Strokes assigned from outside: a new signature with a fresh history. */
3458
+ private adoptStrokes;
3459
+ /** A value assigned from outside: show it as a stored signature, or empty the pad for `""`. */
3460
+ private adoptValue;
3461
+ /** A typed name assigned from outside: a fresh type-mode history, and no typed-at time. */
3462
+ private adoptTypedName;
3463
+ private undoStep;
3464
+ private clearStep;
3465
+ private emit;
3466
+ private emitChange;
3467
+ private get canEdit();
3468
+ private get canDraw();
3469
+ /** Display scale of the signature: 1, or smaller when the pad is now smaller than when the signature began. */
3470
+ private get viewScale();
3471
+ private addPoint;
3472
+ private onPointerDown;
3473
+ private onPointerMove;
3474
+ private onPointerEnd;
3475
+ private onKeyDown;
3476
+ /** Focus target that stays enabled: the pad in draw mode, the name input in type mode. */
3477
+ private focusField;
3478
+ private onUndoClick;
3479
+ private onClearClick;
3480
+ private onToggleMode;
3481
+ private onTypedInput;
3482
+ /** The native change event (blur, or Enter) commits the typed name. */
3483
+ private onTypedChange;
3484
+ private observeSize;
3485
+ private onResize;
3486
+ private scheduleDraw;
3487
+ /** Where a stored signature sits in the `strokes` space: at half its pixel size (it was exported at 2×), shrunk to fit, centred. */
3488
+ private storedPlacement;
3489
+ /** Paints the stored signature and the strokes in one colour. The stored image is recoloured, so it follows the ink colour as strokes do. */
3490
+ private paintInk;
3491
+ private draw;
3492
+ /** Bounding box of the ink in the `strokes` space, line widths included. */
3493
+ private inkBounds;
3494
+ /** The ink trimmed to its bounds plus padding, at 2×, in black. */
3495
+ private renderExport;
3496
+ /** The font of a typed signature: the input's family (our body family, Poppins) in italic. */
3497
+ private typedFont;
3498
+ /** Measures the typed name in its font. Until the font has loaded, measuring (and so the export) uses a fallback; the value is recomputed once it arrives. */
3499
+ private typedMetrics;
3500
+ /** The typed name trimmed to its bounds plus padding, at 2×, in black. */
3501
+ private renderTyped;
3502
+ private typedSVG;
3503
+ private formatSignedAt;
3504
+ /** The pad's accessible name: the field's name, then its state. */
3505
+ private padName;
3506
+ render(): lit_html.TemplateResult<1>;
3507
+ }
3508
+ declare global {
3509
+ interface HTMLElementTagNameMap {
3510
+ "rel-signature-pad": RelSignaturePad;
3511
+ }
3512
+ }
3513
+
2895
3514
  /**
2896
3515
  * "continuous"/"discrete" are the legacy tick-mode values; Figma's Type axis
2897
3516
  * adds "default" (alias of continuous) and "range" (dual-thumb interval).
@@ -2918,7 +3537,7 @@ type SliderSizeInput = SliderSize | SliderSizeAlias;
2918
3537
  * @cssprop --rel-slider-tick-size - Tick-mark diameter in discrete mode; 4 / 5 / 6px for size sm / md / lg
2919
3538
  * @cssprop --rel-slider-bubble-radius - Corner radius of the value bubble; 8px for size sm, otherwise 10px
2920
3539
  */
2921
- declare class RelSlider extends LitElement {
3540
+ declare class RelSlider extends RelElement {
2922
3541
  /** Slider mode: `continuous` (or its alias `default`) accepts any integer value, `discrete` snaps to `step` and draws tick marks, `range` shows two thumbs bounding `value`…`valueEnd`. */
2923
3542
  type: SliderType;
2924
3543
  /**
@@ -3007,22 +3626,22 @@ type SnackbarType = "default" | "success" | "error" | "warning";
3007
3626
  * Snackbar (Figma "Snackbar"): a dark, elevated message bar with an optional tone accent and leading status icon, an optional action button and a close icon, shown via `show` and auto-dismissed after `duration` milliseconds. It is not positioned — place it (fixed or in-flow) yourself. Use it for brief feedback with a single follow-up action ("Undo", "Retry"); use `rel-alert` for persistent messages.
3008
3627
  * @csspart label - The message text container
3009
3628
  * @csspart action - The action button; rendered only when `action` is set
3010
- * @csspart close-button - The close icon button; rendered while `iconclose` is true
3629
+ * @csspart close-button - The close icon button; rendered while `icon-close` is true
3011
3630
  */
3012
- declare class RelSnackbar extends LitElement {
3631
+ declare class RelSnackbar extends RelElement {
3013
3632
  /** Message text. Announced to screen readers through the shared live-region announcer when the snackbar is shown and whenever it changes while shown. The default is a placeholder — always set it. */
3014
3633
  message: string;
3015
3634
  /** Label of the optional action button (Figma trailing button, Static / Small); omitted when unset. Clicking it fires `actionClick` and then dismisses the snackbar. */
3016
3635
  action?: string;
3017
3636
  /**
3018
- * Layout and timing (Figma "Type"): `"default"` (message and actions on one row) | `"longer-action"` (message stacked above the actions, which align to the end, with the auto-dismiss delay doubled so there is time to use the action). Takes precedence over the deprecated `lines` and `longerAction` props.
3637
+ * Layout and timing (Figma "Type"): `"default"` (the default: message and actions on one row) | `"longer-action"` (message stacked above the actions, which align to the end, with the auto-dismiss delay doubled so there is time to use the action). Takes precedence over the deprecated `lines` and `longerAction` props.
3019
3638
  */
3020
3639
  variant?: SnackbarVariant;
3021
- /** @deprecated — use `variant="longer-action"`, which also sets the stacked layout. Doubles the auto-dismiss delay (`duration * 2`) on its own. Ignored when `variant` is set. Logs a one-time console notice. */
3640
+ /** @deprecated — use `variant="longer-action"`. On its own it renders the full longer-action variant (stacked layout and the doubled auto-dismiss delay, `duration * 2`). Ignored when `variant` is set. Logs a one-time console notice. */
3022
3641
  longerAction: boolean;
3023
- /** Shows the trailing close icon button. Defaults to true; because it is a Lit boolean, `iconclose="false"` is still true — set the property to `false` from script to hide the button. */
3642
+ /** Shows the trailing close icon button. Defaults to true; `icon-close="false"` (or the property set to `false`) hides the button. */
3024
3643
  iconClose: boolean;
3025
- /** @deprecated — use `variant`; `lines="double"` maps to `variant="longer-action"`. Sets the stacked layout on its own but not the doubled delay. Ignored when `variant` is set. Logs a one-time console notice. */
3644
+ /** @deprecated — use `variant`; `lines="double"` maps to `variant="longer-action"` (stacked layout and the doubled auto-dismiss delay). Ignored when `variant` is set. Logs a one-time console notice. */
3026
3645
  lines: SnackbarLines;
3027
3646
  /** Auto-dismiss delay in milliseconds (doubled by `variant="longer-action"`), started when `show` becomes true and restarted when `duration` or the layout changes while shown. `0` (or a negative value) disables auto-dismiss. */
3028
3647
  duration: number;
@@ -3053,7 +3672,100 @@ declare class RelSnackbar extends LitElement {
3053
3672
  render(): lit_html.TemplateResult<1>;
3054
3673
  }
3055
3674
 
3056
- /** Control scale, spelled out. Figma has no Size axis on Switch; see the size ramp in the styles. */
3675
+ /** One entry of the split button's menu. */
3676
+ interface SplitButtonItem {
3677
+ /** Identifies the entry in `rel-select`. Unique within `items`. */
3678
+ value: string;
3679
+ /** Visible text of the menu row. */
3680
+ label: string;
3681
+ /** `rel-icon` name drawn before the label, sized with the menu (20 / 18 / 16px). */
3682
+ icon?: string;
3683
+ /** The row stays in the menu (and reachable with the arrow keys) but cannot be chosen; drawn at 40% opacity. */
3684
+ disabled?: boolean;
3685
+ }
3686
+ type SplitButtonVariant = "primary" | "secondary" | "ghost" | "static";
3687
+ type SplitButtonTone = "neutral" | "brand" | "destructive";
3688
+ type SplitButtonSize = "small" | "medium" | "large";
3689
+ /**
3690
+ * New in 1.5.0. A button with a main action and a menu of related actions (the "Combo Button"): one pill in two segments with a 1px divider between them, the main action on the left and a chevron on the right that opens a `rel-menu` of `items`, anchored below and end-aligned. Each segment is its own `<button>` with rel-button's styles for the chosen `variant`, `tone` and `size`, so the heights (32 / 40 / 48), padding, hover, pressed, focus ring and disabled look are rel-button's. The chevron follows the WAI-ARIA menu button pattern and shows its pressed look while the menu is open.
3691
+ * @slot - The main action's content (text, or a `rel-icon` and text). Falls back to `label`.
3692
+ * @csspart main - The main action's native `<button>`.
3693
+ * @csspart toggle - The chevron's native `<button>` (`aria-haspopup="menu"`, `aria-expanded`, named by `menu-label`).
3694
+ * @csspart indicator - The loading spinner on the main action, rendered only while `loading` is set.
3695
+ * @csspart list - The menu's `role="menu"` list (rel-menu's list part): restyle the menu's surface here.
3696
+ * @cssprop --rel-split-button-menu-width - Width of the menu (default `max-content`). The menu is never narrower than the split button.
3697
+ * @cssprop --rel-button-min-width - The main action's minimum width, as on rel-button (80 / 100 / 120px by size).
3698
+ * @fires click - The main action was activated (a native click: pointer, Enter or Space). Clicks on the chevron or the menu do not reach the host. Swallowed while `disabled` or `loading`.
3699
+ * @fires rel-select - A menu entry was chosen with a click, Enter or Space; the menu has closed and focus is back on the chevron. Detail: `{ value: string, item: SplitButtonItem }`.
3700
+ * @fires rel-open-change - The menu opened or closed (chevron, keyboard, Escape, an outside click, Tab away, or a choice). Detail: `{ open: boolean }`.
3701
+ */
3702
+ declare class RelSplitButton extends RelElement {
3703
+ /** Text of the main action. Slotted content replaces it. */
3704
+ label: string;
3705
+ /** Style of both segments, as rel-button's `variant`: `primary` (filled), `secondary` (outlined, the default), `ghost` (no fill or border) or `static` (transparent with a white label and divider, for dark or coloured surfaces; neutral only, as on rel-button). */
3706
+ variant: SplitButtonVariant;
3707
+ /** Colour of both segments, as rel-button's `tone`: `neutral` (default), `brand` or `destructive`. As on rel-button, `brand` changes only the `primary` style. */
3708
+ tone: SplitButtonTone;
3709
+ /** Height of the pill and size of its text and icons, as rel-button's `size`: `small` = 32px, `medium` = 40px (default), `large` = 48px. The menu rows take the same size. */
3710
+ size: SplitButtonSize;
3711
+ /** Dims both segments to rel-button's disabled look (the divider keeps its colour), removes them from the Tab order and keeps the menu closed. */
3712
+ disabled: boolean;
3713
+ /** The main action shows only rel-button's spinner, at its minimum width; its text stays its accessible name (visually hidden) and it is `aria-busy`. Its clicks are blocked and the chevron is disabled, so nothing else starts mid-request; an open menu closes. */
3714
+ loading: boolean;
3715
+ /** The main action is a square of the button's height (32 / 40 / 48), as rel-button's `icon-only`: give it a single icon (`icon`, or a slotted `rel-icon`) and set `aria-label`. */
3716
+ iconOnly: boolean;
3717
+ /** Accessible name of the main action, forwarded to its native `<button>`. Required with `icon-only`, where the main action has no visible text (a console warning names split buttons without one). */
3718
+ ariaLabel: string | null;
3719
+ /** Accessible name of the chevron button (attribute `menu-label`); it has no visible text. */
3720
+ menuLabel: string;
3721
+ /** `rel-icon` name drawn before the main action's text (16 / 18 / 20px by size). */
3722
+ icon: string;
3723
+ /** The menu entries, each `{ value, label, icon?, disabled? }`. Set as a property (or a JSON array attribute). */
3724
+ items: SplitButtonItem[];
3725
+ /** Whether the menu is open. Updated by the component (and announced with `rel-open-change`); set it to open or close the menu from code. */
3726
+ open: boolean;
3727
+ private popoverEl?;
3728
+ private menu?;
3729
+ /** The menu is at least as wide as the split button; measured on open. */
3730
+ private menuMinWidth;
3731
+ /** Right-to-left: the chevron is on the left, so the menu aligns to its start. */
3732
+ private rtl;
3733
+ static styles: (lit.CSSResult | lit.CSSResult[])[];
3734
+ connectedCallback(): void;
3735
+ disconnectedCallback(): void;
3736
+ /** One console warning per element for an icon-only main action with no name. */
3737
+ private warnedNoName;
3738
+ willUpdate(changed: PropertyValues<this>): void;
3739
+ private get variantKey();
3740
+ private get toneKey();
3741
+ private get sizeKey();
3742
+ /** rel-button's class contract: the same names rel-button renders, so its
3743
+ * stylesheet styles both segments. */
3744
+ private segmentClasses;
3745
+ /** Opens or closes the menu and announces it. `focusIndex` picks the row
3746
+ * the menu opens on (negative counts from the end). */
3747
+ private setOpen;
3748
+ private onHostClick;
3749
+ /** Tab or a click elsewhere moved focus out of the split button (its menu
3750
+ * included): the menu closes, focus stays where the user put it. */
3751
+ private onFocusOut;
3752
+ /** rel-popover asks (chevron press, Enter, Space, Escape, outside click);
3753
+ * the split button owns the state and re-emits the event as its own. */
3754
+ private onPopoverOpenChange;
3755
+ private onMenuSideClick;
3756
+ private onToggleKeydown;
3757
+ private onMenuSelect;
3758
+ private onMenuKeydown;
3759
+ render(): lit_html.TemplateResult<1>;
3760
+ private onMainClick;
3761
+ }
3762
+ declare global {
3763
+ interface HTMLElementTagNameMap {
3764
+ "rel-split-button": RelSplitButton;
3765
+ }
3766
+ }
3767
+
3768
+ /** Control scale, spelled out (Figma Switch "Size": Small / Medium / Large). */
3057
3769
  type SwitchSize = "small" | "medium" | "large";
3058
3770
  /** Alternative spellings, not deprecations — the short names work too. */
3059
3771
  type SwitchSizeAlias = "sm" | "md" | "lg";
@@ -3061,14 +3773,20 @@ type SwitchSizeInput = SwitchSize | SwitchSizeAlias;
3061
3773
  /** Which side of the control the label sits on. */
3062
3774
  type SwitchLabelPlacement = "end" | "start";
3063
3775
  /**
3064
- * Toggle switch (Figma "Switch", 5917:1174, Type=Line): a 44×20 control with a
3065
- * thin 10px track and a 20px handle that slides 20px. Wraps a native checkbox
3066
- * with `role="switch"`, so keyboard and screen-reader behaviour are native.
3776
+ * Toggle switch (Figma "Switch", 9059:11244): at the default medium size a
3777
+ * 44×20 control with a thin 10px track and a 20px handle that slides 20px
3778
+ * (`type="line"`), or a 44×24 filled track (`type="pill"`), with the label in
3779
+ * body-md beside it. Hover and pressed cover the whole component, and the
3780
+ * keyboard focus ring is a square 1.5px outline around the control and its
3781
+ * label together. Wraps a native checkbox with `role="switch"`, so keyboard
3782
+ * and screen-reader behaviour are native.
3783
+ * Form-associated, so an outer `<label>` (wrapping it, or `for=`) toggles it
3784
+ * and names it; it still sets no form value and is not submitted.
3067
3785
  * @slot - Rich label content beside the control, as an alternative to the `label` property. Clicking it toggles the switch.
3068
- * @csspart root - The `<label>` holding the control and its optional text; carries the `label-placement` order
3786
+ * @csspart root - The `<label>` holding the control and its optional text; carries the `label-placement` order and the keyboard focus ring
3069
3787
  * @csspart base - The control box around the hidden input and the track
3070
3788
  * @csspart track - The thin rail; the handle is its `::before` pseudo-element
3071
- * @csspart label - The text beside the control; not rendered when there is neither a `label` nor slotted content
3789
+ * @csspart label - The text beside the control (body-sm / body-md / body-lg by `size`; `--rel-text-disabled` while disabled); not rendered when there is neither a `label` nor slotted content
3072
3790
  * @cssprop --rel-switch-track-width - Overall control width; defaults to the `size` ramp (36 / 44 / 52px)
3073
3791
  * @cssprop --rel-switch-track-height - Overall control height; defaults to the `size` ramp — 16 / 20 / 24px for `type="line"`, 20 / 24 / 28px for `type="pill"`
3074
3792
  * @cssprop --rel-switch-thumb-size - Handle diameter; defaults to the `size` ramp (16 / 20 / 24px), and the handle always travels its own width minus the 2px inset on each side
@@ -3077,23 +3795,23 @@ type SwitchLabelPlacement = "end" | "start";
3077
3795
  * @cssprop --rel-switch-knob-off - Handle colour when unchecked; default `var(--rel-text-static-light)`
3078
3796
  * @cssprop --rel-switch-disabled - Handle colour while disabled; default `var(--rel-bg-neutral-elevated-1)`
3079
3797
  */
3080
- declare class RelSwitch extends LitElement {
3798
+ declare class RelSwitch extends RelElement {
3799
+ /** Form-associated only so an outer `<label>` has a control (see utils/outer-label.ts). No form value is set. */
3800
+ static formAssociated: boolean;
3081
3801
  /** Whether the switch is on. Set it to control the state programmatically; user toggles update it and fire `rel-change`. */
3082
3802
  checked: boolean;
3083
3803
  /**
3084
3804
  * Shape (Figma "Type"): `"line"` (default) is the 44x20 control with a thin
3085
3805
  * 40x10 rail and the handle riding on it; `"pill"` is the 44x24 filled track
3086
- * with the handle inside it.
3087
- * NOTE: Figma's own default is Pill. We default to `line` deliberately —
3088
- * flipping it would restyle every existing switch — so this is a recorded
3089
- * divergence from the design default, not an oversight.
3806
+ * with the handle inside it. Pressing squashes the handle; only `line`
3807
+ * also shows a pressed disc behind it.
3090
3808
  */
3091
3809
  type: "line" | "pill";
3092
- /** Disables interaction and swaps in the dulled rail / handle fills; the handle shadow is removed. */
3810
+ /** Disables interaction and swaps in the dulled rail / handle fills (for both types, checked or not) and the disabled label colour; the handle shadow is removed. An ancestor `<fieldset disabled>` disables it the same way, without changing this property. */
3093
3811
  disabled: boolean;
3094
3812
  /** Arbitrary string echoed in the `rel-change` event detail — useful when one handler serves several switches. */
3095
3813
  value: string;
3096
- /** Accessible name for the native input. Without it the switch has no label of its own, so pair it with a visible `<label>` or set this. */
3814
+ /** Accessible name for the native input when the switch has no visible text. Prefer `label`, slotted text or an outer `<label>` (wrapping the switch, or `for=`), which are visible and clickable. */
3097
3815
  fieldAriaLabel?: string;
3098
3816
  /** Visible text beside the control. Clicking it toggles the switch, and it becomes the accessible name — use `fieldAriaLabel` instead when the switch has no visible text. For rich content (a bold word, an icon) slot it in the default slot rather than setting this. */
3099
3817
  label?: string;
@@ -3109,9 +3827,9 @@ declare class RelSwitch extends LitElement {
3109
3827
  * are alternative spellings and stay supported — normalised to the full
3110
3828
  * words, not deprecated.
3111
3829
  *
3112
- * Figma's Switch has axes Type / State / Enabled only, with no Size, so this
3113
- * ramp is ours. `medium` is exactly the control that shipped before it
3114
- * existed, so an existing switch is unchanged.
3830
+ * Matches Figma's Size axis: controls 36 / 44 / 52px wide, labels body-sm /
3831
+ * body-md / body-lg (12 / 14 / 16px). The default stays `medium` (Figma's
3832
+ * own default is Large).
3115
3833
  *
3116
3834
  * It does NOT respond to viewport width. Picking a size per breakpoint is the
3117
3835
  * consuming application's decision — a control that silently resized itself
@@ -3121,8 +3839,15 @@ declare class RelSwitch extends LitElement {
3121
3839
  size: SwitchSizeInput;
3122
3840
  private _onChange;
3123
3841
  private _onTouched;
3124
- private isFocused;
3125
- private isPressed;
3842
+ private readonly internals;
3843
+ /** A click on an outer label arrives at the host; route it through the
3844
+ * native input so it toggles exactly as a direct click does (one
3845
+ * `change`, one `rel-change`). A disabled input ignores the click. */
3846
+ private readonly outerLabel;
3847
+ /** Own `disabled`, or an ancestor `<fieldset disabled>`. */
3848
+ private get isDisabled();
3849
+ /** @internal Form-associated lifecycle: an ancestor `<fieldset disabled>` disables the control too (utils/outer-label.ts, 3). */
3850
+ formDisabledCallback(): void;
3126
3851
  private hasSlottedLabel;
3127
3852
  /** Alternative spellings, NOT deprecations — no console notice. Normalising
3128
3853
  * keeps the reflected attribute canonical so the :host([size=…]) rules only
@@ -3136,10 +3861,7 @@ declare class RelSwitch extends LitElement {
3136
3861
  registerOnTouched(fn: () => void): void;
3137
3862
  /** @internal Angular ControlValueAccessor bridge. */
3138
3863
  writeValue(checked: boolean): void;
3139
- private handleFocus;
3140
3864
  private handleBlur;
3141
- private handleMouseDown;
3142
- private handleMouseUp;
3143
3865
  render(): lit_html.TemplateResult<1>;
3144
3866
  /** Rich label content can be slotted instead of set as text; this keeps the
3145
3867
  * label span out of the layout entirely when neither is present, so an
@@ -3161,11 +3883,15 @@ interface TabItem {
3161
3883
  * is an explicit item height — 32 / 40 / 48px (Figma Tab Item S/M/L).
3162
3884
  */
3163
3885
  type TabSize = "sm" | "md" | "lg" | "small" | "medium" | "large";
3886
+ /** Layout of the tab list: a row above the panel (`horizontal`, default) or a column beside it (`vertical`). */
3887
+ type TabOrientation = "horizontal" | "vertical";
3888
+ /** Where each tab's icon sits relative to its label. */
3889
+ type TabIconPlacement = "start" | "top";
3164
3890
  /**
3165
3891
  * Declarative tab definition for `<rel-tab>`. Renders nothing itself — place
3166
3892
  * it in the parent's `items` slot and the parent reads its properties.
3167
3893
  */
3168
- declare class RelTabItem extends LitElement {
3894
+ declare class RelTabItem extends RelElement {
3169
3895
  /** Text shown on the tab. */
3170
3896
  label: string;
3171
3897
  /** Icon name from the generated icon dictionary, rendered before the label (Figma "Show Icon" / "Icon"). Sized 20 / 18 / 16px with the tab's own size step. */
@@ -3190,14 +3916,15 @@ declare class RelTabItem extends LitElement {
3190
3916
  * @csspart list - The `role="tablist"` row
3191
3917
  * @csspart item - Each `role="tab"` element
3192
3918
  * @csspart label - The text span inside each tab
3919
+ * @cssprop --rel-tab-list-width - Width of the tab list when `orientation="vertical"`; default `max-content`
3193
3920
  * @cssprop --rel-tab-active-color - Underline colour of the active tab; default `var(--rel-bg-brand)`
3194
3921
  * @cssprop --rel-tab-underline-height - Thickness of the active underline; default 1px (`--rel-stroke-sm`, the weight Figma draws). Set it to restore the heavier 3px bar shipped previously.
3195
3922
  * @cssprop --rel-tab-text-default - Label colour of the active tab; default `var(--rel-text-default)`
3196
- * @cssprop --rel-tab-text-hover - Label colour while hovering an enabled tab; default `var(--rel-text-label)`
3923
+ * @cssprop --rel-tab-text-hover - Label colour while hovering an enabled tab; default `var(--rel-text-default)`
3197
3924
  * @cssprop --rel-tab-text-disabled - Label colour of a disabled tab; default `var(--rel-text-disabled)`
3198
- * @cssprop --rel-tab-hover-color - Background of an inactive tab on hover; default `var(--rel-bg-neutral-elevated-1)`
3925
+ * @cssprop --rel-tab-hover-color - Background of an inactive tab on hover; default `transparent` (the design darkens the label instead). Set it to bring a hover fill back.
3199
3926
  */
3200
- declare class RelTab extends LitElement {
3927
+ declare class RelTab extends RelElement {
3201
3928
  /** Tab definitions as `{ label, badge?, badgeDot?, disabled? }` objects — normally set as a property (as an attribute it must be a JSON string). Assign a new array to update; mutating in place does not re-render. Ignored when `<rel-tab-item>` children are slotted into `items`. */
3202
3929
  tabs: TabItem[];
3203
3930
  /** Zero-based index of the selected tab. Set it to switch tabs programmatically (no event is fired for programmatic changes). */
@@ -3205,6 +3932,10 @@ declare class RelTab extends LitElement {
3205
3932
  /** Tab size — styled via :host([size]) rules. Explicit item heights
3206
3933
  * 32 / 40 / 48px (audit 34); `small|medium|large` alias `sm|md|lg`. */
3207
3934
  size: TabSize;
3935
+ /** New in 1.5.0. Tab list layout: `horizontal` (default, a row above the panel) or `vertical` (a column beside the panel as wide as its longest label, tabs 24px apart along a rule on the list's inline-start edge, the active bar on that edge, labels that wrap rather than truncate; Up / Down move between tabs). There is no built-in breakpoint: switch to horizontal on small screens in your app. */
3936
+ orientation: TabOrientation;
3937
+ /** New in 1.5.0. Icon position in every tab: `start` (default, before the label) or `top` (stacked above the label, with the badge beside the label). Stacked tabs are 76 / 64 / 52px tall at large / medium / small. Works in both orientations. */
3938
+ iconPlacement: TabIconPlacement;
3208
3939
  private _tabItems;
3209
3940
  /** Stable id prefix linking tabs to the shared tabpanel. */
3210
3941
  private tabsId;
@@ -3244,17 +3975,17 @@ declare class RelTab extends LitElement {
3244
3975
  * @csspart option - Each hour / minute / second / AM-PM entry button
3245
3976
  * @csspart footer - The Now / Cancel / OK row
3246
3977
  * @csspart helper - The helper / error text live region under the field
3247
- * @cssprop --rel-overlay-fallback-z - z-index of the panel on the NO-POPOVER-API fallback path only. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with rel-select, rel-autocomplete and the other pickers so the whole overlay family can be moved from one place; default 1001.
3978
+ * @cssprop --rel-overlay-fallback-z - z-index of the panel on the **no-Popover-API fallback path only**. Wherever the browser top layer is available the panel is promoted into it and this value is inert, because top-layer elements are not in the page stacking order at all. Shared with the other overlays (select, autocomplete, the three pickers) so the whole family can be moved from one place.
3248
3979
  * @fires rel-focus - Fired when the text input receives focus. No detail.
3249
3980
  */
3250
- declare class RelTimePicker extends LitElement {
3981
+ declare class RelTimePicker extends RelElement {
3251
3982
  /** Committed time, 24-hour "HH:mm" or "HH:mm:ss" (e.g. "09:30", "17:05:30"). */
3252
3983
  value: string | null;
3253
3984
  /** Placeholder shown in the field while no value is committed and nothing is typed. */
3254
3985
  placeholder: string;
3255
3986
  /** Field label rendered above the trigger; also the input's accessible name (unset → "Select time"). */
3256
3987
  label: string | null;
3257
- /** Disables the field (0.6 opacity, no pointer events); the panel cannot open. */
3988
+ /** Disables the field (no pointer events) and the panel cannot open. The field looks exactly like a disabled `rel-textfield`: the whole field at 0.65 opacity, an elevated-2 box with the accent border, the label in `--rel-text-label` and the value and placeholder in `--rel-text-caption` (changed in 1.5.0: they were `--rel-text-disabled` under the 0.65, which left them barely readable). */
3258
3989
  disabled: boolean;
3259
3990
  /** Renders the `*` mark after the label and sets `aria-required`; not enforced by the component. */
3260
3991
  required: boolean;
@@ -3288,7 +4019,7 @@ declare class RelTimePicker extends LitElement {
3288
4019
  size: "small" | "medium" | "large";
3289
4020
  /** Visual style; only `"outlined"` exists today. */
3290
4021
  variant: "outlined";
3291
- /** Fills the parent width (the host is inline-block with intrinsic width otherwise). The attribute is lowercase `fullwidth`. */
4022
+ /** Fills the parent width (the host is inline-block with intrinsic width otherwise). The attribute is `full-width`. */
3292
4023
  fullWidth: boolean;
3293
4024
  private draftHour;
3294
4025
  private draftMinute;
@@ -3316,7 +4047,7 @@ declare class RelTimePicker extends LitElement {
3316
4047
  private showTopLayer;
3317
4048
  private hideTopLayer;
3318
4049
  private onReposition;
3319
- private onPageScroll;
4050
+ private scrollWatch;
3320
4051
  private updatePopoverPosition;
3321
4052
  /** Focus the selected (or first enabled) option in the columns. */
3322
4053
  private focusPanelSelection;
@@ -3393,8 +4124,11 @@ declare global {
3393
4124
  }
3394
4125
  }
3395
4126
 
3396
- /** Canonical semantic tones (Figma "Tone"): neutral | destructive. */
3397
- type ToastTone = "neutral" | "destructive";
4127
+ /**
4128
+ * Canonical semantic tones: neutral | info | success | destructive, as on
4129
+ * Figma's Toast "Tone" axis; the filled tones reuse rel-alert's solid tone tokens.
4130
+ */
4131
+ type ToastTone = "neutral" | "info" | "success" | "destructive";
3398
4132
  /** Deprecated spellings: "default" → neutral; "error" | "danger" | "critical" → destructive. */
3399
4133
  type ToastToneAlias = "default" | "error" | "danger" | "critical";
3400
4134
  type ToastToneInput = ToastTone | ToastToneAlias;
@@ -3422,22 +4156,23 @@ type ToastSizeInput = ToastSize | ToastSizeAlias;
3422
4156
  * independently. Behaviour change: five toasts at one position used to render on
3423
4157
  * top of each other; now three show and two wait.
3424
4158
  * @csspart base - The `role="status"` toast container that carries the tone class
4159
+ * @csspart icon - The 20px leading icon of a filled tone (`error` on destructive, `check-circle` on success, `info` on info); a neutral toast has none. New in 1.5.0
3425
4160
  * @csspart label - The message text span
3426
- * @csspart action - The action button; rendered only when `actionlabel` is set
4161
+ * @csspart action - The action button; rendered only when `action-label` is set
3427
4162
  * @csspart close-button - The dismiss button; rendered while `dismissible` is true
3428
4163
  * @cssprop --rel-toast-z - `z-index` of the fixed host (default `9000`); raise it when a modal or drawer layer covers the toast
3429
4164
  * @cssprop --rel-z-toast - Deprecated alias of `--rel-toast-z`, still read as its fallback
3430
4165
  */
3431
- declare class RelToast extends LitElement {
4166
+ declare class RelToast extends RelElement {
3432
4167
  /** Text of the toast. It is also announced to screen readers through the shared live-region announcer each time `show` becomes true. */
3433
4168
  message: string;
3434
4169
  /**
3435
- * Semantic tone (Figma "Tone"): `"neutral"` (default: neutral surface) | `"destructive"` (danger fill, announced assertively). `"default"` is a deprecated alias of `"neutral"`; `"error"`, `"danger"` and `"critical"` are deprecated aliases of `"destructive"`; each logs a one-time console notice. Takes precedence over the deprecated `type` prop.
4170
+ * Semantic tone (Figma "Tone"): `"neutral"` (default: neutral surface) | `"info"` (info fill) | `"success"` (success fill) | `"destructive"` (danger fill, announced assertively). The three filled tones use the same fill and text tokens as rel-alert's solid style, so a toast matches an alert of the same tone; info and success are announced politely. The three filled tones show a 20px leading icon (`error`, `check-circle`, `info`; part `icon`, hidden from assistive technology); the neutral tone has none (new in 1.5.0). Figma's Toast has all four tones. An unknown value renders as `"neutral"`. `"default"` is a deprecated alias of `"neutral"`; `"error"`, `"danger"` and `"critical"` are deprecated aliases of `"destructive"`; each logs a one-time console notice. Takes precedence over the deprecated `type` prop.
3436
4171
  */
3437
4172
  tone?: ToastToneInput;
3438
4173
  /** @deprecated Deprecated since 1.4.7 — alias of `tone`; use `tone`. Same values as `tone`; `type="error"` maps to `tone="destructive"` and `type="default"` to `tone="neutral"`. Ignored when `tone` is set. Logs a one-time console notice. */
3439
4174
  type?: ToastToneInput;
3440
- /** Read-only canonical tone actually rendered: explicit `tone` wins, else `type`, else `"neutral"`; deprecated aliases are resolved to `"neutral"` / `"destructive"`. */
4175
+ /** Read-only canonical tone actually rendered: explicit `tone` wins, else `type`, else `"neutral"`; deprecated aliases are resolved to `"neutral"` / `"destructive"`, and an unknown value to `"neutral"`. */
3441
4176
  get resolvedTone(): ToastTone;
3442
4177
  /** Label of the optional action button rendered between the message and the dismiss button; the button is omitted when unset. Clicking it fires `action` and then closes the toast. */
3443
4178
  actionLabel?: string;
@@ -3445,7 +4180,7 @@ declare class RelToast extends LitElement {
3445
4180
  duration: number;
3446
4181
  /** Whether the toast is visible. Set it to true (or call `open()`) to show the toast and start the auto-dismiss timer; it is set back to false by `close()`, the dismiss button, the action button and the timer. */
3447
4182
  show: boolean;
3448
- /** Shows the trailing dismiss button (Figma "Show Close Button"). Defaults to true; because it is a Lit boolean, `dismissible="false"` is still true — set the property to `false` from script to hide the button. */
4183
+ /** Shows the trailing dismiss button (Figma "Show Close Button"). Defaults to true; `dismissible="false"` (or the property set to `false`) hides the button. */
3449
4184
  dismissible: boolean;
3450
4185
  /**
3451
4186
  * Where the toast is anchored: `bottom-center` (default, unchanged from
@@ -3513,12 +4248,12 @@ type TooltipType = "single-line" | "multi-line";
3513
4248
  type TooltipTone = "inverse" | "neutral";
3514
4249
  /**
3515
4250
  * Hover/focus tooltip (Figma "Tooltip"): wraps a trigger element and shows a short inverse-coloured text bubble above, below, left or right of it. Use it for supplementary hints on icons and truncated labels only — the text is not reachable on touch and must never be the only place critical information lives.
3516
- * @slot - The trigger element (one focusable element such as a button or icon button). It receives `aria-describedby` pointing at the bubble.
4251
+ * @slot - The trigger element (one focusable element such as a button or icon button). The control that takes focus — the slotted element itself, or the focusable control inside a rel-* trigger such as rel-button — receives `text` as its `aria-description`, so screen readers announce it.
3517
4252
  * @csspart panel - The tooltip bubble (`role="tooltip"`): background, padding, radius, shadow and max-width.
3518
4253
  * @csspart indicator - The triangular tail pointing at the trigger, rendered only when `show-tail` is set.
3519
4254
  * @cssprop --rel-tooltip-offset - Gap between the trigger and the bubble; defaults to `6px`, or `13px` when `show-tail` is set so the tail clears the trigger.
3520
4255
  */
3521
- declare class RelTooltip extends LitElement {
4256
+ declare class RelTooltip extends RelElement {
3522
4257
  /** Plain-text content of the bubble. Empty text still renders an (empty) bubble on hover, so always set it. */
3523
4258
  text: string;
3524
4259
  /** Side of the trigger the bubble appears on, centred and 6px away (13px when `show-tail` is set, so the tail clears the trigger; override with `--rel-tooltip-offset`): `above`, `below`, `left` or `right`. No collision detection — pick a side with room in the viewport. */
@@ -3530,6 +4265,10 @@ declare class RelTooltip extends LitElement {
3530
4265
  /** Bubble appearance: `inverse` (default) is the original inverse-surface bubble, unchanged; `neutral` is a light surface — `--rel-bg-neutral` with `--rel-text-default` and a 1px `--rel-border-default`, tail included. Figma has no such variant; this is a deliberate addition. */
3531
4266
  tone: TooltipTone;
3532
4267
  private uniqueId;
4268
+ /** The element whose description is the tooltip text. */
4269
+ private describedEl;
4270
+ /** The value this component wrote, so it only ever removes its own. */
4271
+ private appliedDescription;
3533
4272
  static styles: lit.CSSResult;
3534
4273
  connectedCallback(): void;
3535
4274
  disconnectedCallback(): void;
@@ -3538,10 +4277,143 @@ declare class RelTooltip extends LitElement {
3538
4277
  private onKeydown;
3539
4278
  private clearDismissed;
3540
4279
  firstUpdated(): void;
4280
+ updated(changed: Map<string, unknown>): void;
4281
+ /**
4282
+ * Screen readers announce `text` as the trigger's DESCRIPTION. It used to
4283
+ * be wired with aria-describedby pointing at the bubble's id, but the
4284
+ * trigger is light DOM and the bubble lives in this shadow root: an IDREF
4285
+ * cannot cross that boundary, so nothing was ever announced. aria-description
4286
+ * carries the text itself, and it goes on the element that actually takes
4287
+ * focus — the slotted element, or, for a rel-* trigger such as rel-button,
4288
+ * the first focusable control inside its shadow root (the host itself is
4289
+ * not in the focus order and its description would never be read).
4290
+ */
3541
4291
  private linkTrigger;
4292
+ private syncDescription;
4293
+ private clearDescription;
3542
4294
  render(): lit_html.TemplateResult<1>;
3543
4295
  }
3544
4296
 
4297
+ /** One node of a `rel-tree`. */
4298
+ interface TreeItem {
4299
+ /** Identifies the item in `selected`, `selected-values`, `expanded-values` and every event. Unique within the tree. */
4300
+ value: string;
4301
+ /** Visible label: one line, truncated with an ellipsis (the full text is the row's tooltip). */
4302
+ label: string;
4303
+ /** `rel-icon` name drawn before the label (Figma "Show Icon"), 16px. */
4304
+ icon?: string;
4305
+ /** Child items. A non-empty array makes the item a parent with an expand / collapse caret. */
4306
+ children?: TreeItem[];
4307
+ /** Seeds `expanded-values` when the tree first gets its items. */
4308
+ expanded?: boolean;
4309
+ /**
4310
+ * Children are loaded on demand. While `children` is undefined the item shows a caret; expanding it fires `rel-load-children` and marks the row `aria-busy` until you set its `children` (an empty array when it has none, which makes it a leaf) and assign `items` again.
4311
+ */
4312
+ lazy?: boolean;
4313
+ /** The row stays focusable but cannot be selected, checked or expanded (Figma State=Disabled, 40% opacity). A parent's check cascade skips it. */
4314
+ disabled?: boolean;
4315
+ }
4316
+ type TreeSelection = "single" | "multiple" | "none";
4317
+ /**
4318
+ * Hierarchical list whose parents expand and collapse (Figma "Nested Tree"), rendered from the `items` array: 24px rows with hierarchy guides, an expand / collapse caret on parents, an optional icon and a one-line label. The tree is one Tab stop; the arrow keys move, expand and collapse as in the WAI-ARIA Tree View pattern. It has no background, padding or radius of its own: place it inside the surface that hosts it.
4319
+ * @csspart tree - The `role="tree"` list.
4320
+ * @csspart item - Each `role="treeitem"` element; it contains the item's row and, when expanded, the group of its children.
4321
+ * @csspart row - The visible row of an item (guides, caret, checkbox, icon, label). Carries the hover, pressed, selected and focus styles.
4322
+ * @csspart guide - One hierarchy-guide column, one per level of indentation.
4323
+ * @csspart caret - The expand / collapse caret of a parent row.
4324
+ * @csspart checkbox - The check glyph of a row in `multiple` mode (Figma .Check Control: unchecked, checked, indeterminate).
4325
+ * @csspart icon - The optional leading icon.
4326
+ * @csspart label - The label text.
4327
+ * @csspart group - The `role="group"` list that holds an expanded item's children.
4328
+ * @cssprop --rel-tree-row-height - Row height (default 24px, Figma .Tree Item).
4329
+ * @cssprop --rel-tree-indent - Width of one level of indentation, one guide column (default 24px).
4330
+ * @cssprop --rel-tree-guide-color - Colour of the hierarchy guides (default `var(--rel-border-subtle)`).
4331
+ * @fires rel-change - The user changed the selection with Enter, Space or a click: picked a row in `single` mode (detail `{ value: string }`) or checked / unchecked one in `multiple` mode (detail `{ value: string[] }`, the new `selected-values`).
4332
+ * @fires rel-expanded-change - The user expanded or collapsed a parent. Detail: `{ value: string; expanded: boolean }`.
4333
+ * @fires rel-load-children - A `lazy` item without `children` was expanded (by the user, `expanded` or `expanded-values`). Load its children, set them on the item and assign `items` again. Fired once per item until its children arrive. Detail: `{ value: string }`.
4334
+ */
4335
+ declare class RelTree extends RelElement {
4336
+ /** The tree's items, each `{ value, label, icon?, children?, expanded?, disabled?, lazy? }`. Set as a property or as a JSON array attribute. */
4337
+ items: TreeItem[];
4338
+ /** Selection mode: `single` (default; the selected row has the Figma Active style and `aria-selected="true"`), `multiple` (a checkbox on every row, see `selected-values`) or `none` (navigation only: Enter, Space and clicks on a parent row expand or collapse it). */
4339
+ selection: TreeSelection;
4340
+ /** `value` of the selected item in `single` mode; `""` when nothing is selected. Updated on every user selection. */
4341
+ selected: string;
4342
+ /**
4343
+ * Values of the checked items in `multiple` mode. Checking a parent checks every enabled descendant and unchecking it clears them; a parent is checked when all its children are, and shows the indeterminate glyph (`aria-checked="mixed"`) when only some are. The array lists every checked value, parents included, and is rewritten in that form when you set it: a parent's value checks its whole subtree.
4344
+ */
4345
+ selectedValues: string[];
4346
+ /** Values of the expanded parents. Seeded from the items' `expanded` flags, updated whenever the user expands or collapses a row; set it to control expansion. */
4347
+ expandedValues: string[];
4348
+ /** Draws the hierarchy guides (│ ├ └) in the indentation. On by default; `show-guides="false"` hides them and keeps the indentation. */
4349
+ showGuides: boolean;
4350
+ /** Accessible name of the tree. The tree renders no visible label of its own, so set this (a console warning names trees without one). */
4351
+ fieldAriaLabel?: string;
4352
+ /** The item that holds the Tab stop (roving tabindex). */
4353
+ private focusedValue;
4354
+ private typeahead;
4355
+ private typeaheadTimer?;
4356
+ private seededExpansion;
4357
+ private warnedNoName;
4358
+ private rtl;
4359
+ private labelIds;
4360
+ /** Derived check state of every item in `multiple` mode. */
4361
+ private checks;
4362
+ /** Lazy items whose children were requested and have not arrived (`aria-busy`). */
4363
+ private loading;
4364
+ private pendingLoads;
4365
+ static styles: lit.CSSResult;
4366
+ private isParent;
4367
+ /** A lazy item whose children have not been set yet. */
4368
+ private isUnloaded;
4369
+ /** Shows a caret: has children, or may have them once loaded. */
4370
+ private canExpand;
4371
+ private isExpanded;
4372
+ /** Every item with its parent, depth first. */
4373
+ private allRows;
4374
+ /** The rows the user can see, in reading order. */
4375
+ private visibleRows;
4376
+ /** Check state of every item, from the checked leaves (a parent's value counts as all of its leaves). */
4377
+ private deriveChecks;
4378
+ private checkedValues;
4379
+ /** Normalises `selectedValues` to the derived form: every fully checked value, parents included. */
4380
+ private syncChecks;
4381
+ /** Checks or clears every enabled leaf under `item` (disabled subtrees keep their state). */
4382
+ private toggleCheck;
4383
+ protected willUpdate(changed: PropertyValues<this>): void;
4384
+ protected updated(changed: PropertyValues<this>): void;
4385
+ disconnectedCallback(): void;
4386
+ /** Expands every parent that has children (lazy items whose children have not been loaded stay closed). */
4387
+ expandAll(): void;
4388
+ /** Collapses every parent. */
4389
+ collapseAll(): void;
4390
+ private emit;
4391
+ private setExpanded;
4392
+ /** Enter, Space or a click on the row. */
4393
+ private activate;
4394
+ private focusRow;
4395
+ private onRowClick;
4396
+ /** A row can get focus without the keyboard or a click (`focus()`, a screen reader's cursor): it takes the Tab stop, so keys act on it. */
4397
+ private onFocusIn;
4398
+ private onKeyDown;
4399
+ /** Moves to the next visible row whose label starts with the typed text; repeating one character cycles through the matches. */
4400
+ private typeAhead;
4401
+ /**
4402
+ * `more[i]` says whether the ancestor at depth i has a sibling below it.
4403
+ * Level 1 has no guide column; below that there is one column per ancestor
4404
+ * level (a line where that ancestor continues, blank where it ended) and
4405
+ * a last column for the row's own connector (branch, or end on a last child).
4406
+ */
4407
+ private renderItems;
4408
+ private renderItem;
4409
+ render(): TemplateResult<1>;
4410
+ }
4411
+ declare global {
4412
+ interface HTMLElementTagNameMap {
4413
+ "rel-tree": RelTree;
4414
+ }
4415
+ }
4416
+
3545
4417
  /** Per-file lifecycle state. Mirrors the Figma "Upload File Row" variants 1:1. */
3546
4418
  type UploadStatus = "ready" | "uploading" | "success" | "error";
3547
4419
  /**
@@ -3568,7 +4440,8 @@ interface UploadFile {
3568
4440
  }
3569
4441
  interface UploadRejection {
3570
4442
  name: string;
3571
- reason: "format" | "size";
4443
+ /** `count`: beyond `max-files` (new in 1.5.0). */
4444
+ reason: "format" | "size" | "count";
3572
4445
  message: string;
3573
4446
  }
3574
4447
  interface UploaderContext {
@@ -3608,7 +4481,8 @@ interface UploadState {
3608
4481
  *
3609
4482
  * @element rel-upload
3610
4483
  * @fires rel-files-added - Fired after files are picked or dropped and validated. Detail: `{ added: UploadFile[], rejected: UploadRejection[] }`.
3611
- * @fires rel-invalid - Fired just before `rel-files-added` when at least one picked file fails `accept` / `max-size`. Detail: `{ rejected: UploadRejection[] }`.
4484
+ * @fires rel-invalid - Fired just before `rel-files-added` when at least one picked file fails `accept` / `max-size` / `max-files`. Detail: `{ rejected: UploadRejection[] }`.
4485
+ * @fires rel-file-replaced - New in 1.5.0. Fired once per list entry that a newly picked file with the same name replaced (the replace itself is unchanged: names stay unique). The component also announces it and shows an info notice in its alert slot. Detail: `{ name: string, previous: UploadFile, next: UploadFile }`.
3612
4486
  * @fires rel-change - Fired whenever the file list changes (files added, a row removed, `reset()`). Detail: `{ files: UploadFile[] }`.
3613
4487
  * @fires rel-remove - Fired when a row is removed via its delete button or `removeFile()`. Detail: `{ id: string, file?: File }`.
3614
4488
  * @fires rel-upload - Fired when Upload is pressed (or `upload()` is called) with the rows still `ready`; in controlled mode the app performs the transfer. Detail: `{ files: UploadFile[] }`.
@@ -3617,18 +4491,31 @@ interface UploadState {
3617
4491
  * @fires rel-progress - Fired on every progress tick in self-driving mode. Detail: `{ id: string, progress: number (0–100), loaded: number, total: number }` (bytes).
3618
4492
  * @fires rel-file-complete - Fired when one file finishes uploading in self-driving mode. Detail: `{ id: string, file: File }`.
3619
4493
  * @fires rel-file-error - Fired when one file's `uploader` rejects in self-driving mode. Detail: `{ id: string, file: File, error: string }`.
3620
- * @fires rel-complete - Fired when the whole self-driving batch settles without being cancelled. Detail: `{ succeeded: UploadFile[], failed: UploadFile[] }`.
4494
+ * @fires rel-complete - Deprecated: use `rel-upload-complete`, which fires once per batch in both modes; removed in 3.0. It still fires as before, and listening for it logs a one-time console warning. Fired when the whole self-driving batch settles without being cancelled. Detail: `{ succeeded: UploadFile[], failed: UploadFile[] }` (every success and error row in the list). Self-driving mode only; `rel-upload-complete` covers both modes.
4495
+ * @fires rel-upload-complete - New in 1.5.0. Fired once per batch when nothing is uploading any more, in BOTH modes: in self-driving mode when the `uploader` calls settle, in controlled mode when the app moves the last `uploading` row it set to `success` or `error`. Not fired after `cancel()`. The component announces the result politely ("3 files uploaded", "2 uploaded, 1 failed"), and when nothing failed it shows a success alert in its alert slot ("3 files uploaded"; the delay warning and the failure alert come first). It shows no toast: listen for this event to show your own (for example a success `rel-toast`). Detail: `{ succeeded: UploadFile[], failed: UploadFile[] }`, the rows of this batch only.
3621
4496
  * @fires rel-state - Fired when the derived `state` snapshot changes (and once after connecting). Detail: `UploadState`.
3622
4497
  * @cssprop --rel-upload-browse-hover-bg - Background of the Browse button on hover. Defaults to `transparent`, which is what the underlying `rel-button` secondary style draws (Figma's outline button reacts on its border, not its fill), so this only takes effect when you set it.
3623
4498
  * @csspart browse - The `rel-button` that opens the file picker (Figma "Browse Button": Size=Medium, Style=Secondary).
3624
4499
  */
3625
- declare class RelUpload extends LitElement {
4500
+ declare class RelUpload extends RelElement {
4501
+ /**
4502
+ * `rel-complete` is deprecated (removal in 3.0): adding a listener for it on
4503
+ * the element logs a one-time notice (once per page). Lit `@rel-complete`,
4504
+ * Angular's EventMapper and React 19 all register here. The event itself is
4505
+ * unchanged; listeners on an ancestor that rely on bubbling are not seen.
4506
+ * @internal DOM plumbing, not component API (kept out of the README and the manifest).
4507
+ */
4508
+ addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
4509
+ /** @internal */
4510
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
3626
4511
  /** Comma-separated accept list, e.g. ".pdf,.png,.jpg" or "image/*". */
3627
4512
  accept: string;
3628
4513
  /** Max size per file as a human string ("10MB", "500kb"). Empty → no limit. */
3629
4514
  maxSize: string;
3630
4515
  /** Allow more than one file. */
3631
4516
  multiple: boolean;
4517
+ /** New in 1.5.0. With `multiple`, the most files the list may hold; `0` (the default) means no limit. Files beyond it become error rows ("only 5 files allowed") and `rel-invalid` rejections with `reason: "count"`. Rows already in the list count first, then new files in selection order; rows rejected by `accept`, `max-size` or the limit hold no place. `retry()` checks the limit again, so removing rows lets a rejected one through. Ignored without `multiple` (one file at a time). */
4518
+ maxFiles: number;
3632
4519
  /** Disables the whole control. */
3633
4520
  disabled: boolean;
3634
4521
  /** Overrides the derived "PDF, PNG or JPG - up to 10 MB per file" hint. */
@@ -3640,6 +4527,9 @@ declare class RelUpload extends LitElement {
3640
4527
  /**
3641
4528
  * The file list. Set it to drive the component in controlled mode; the
3642
4529
  * component also maintains it itself as files are added and removed.
4530
+ * A row whose `file` is an image (`image/*`) shows a 40px preview in place
4531
+ * of the file icon (new in 1.5.0); its object URL is revoked when the row
4532
+ * leaves the list and when the element disconnects.
3643
4533
  */
3644
4534
  files: UploadFile[];
3645
4535
  /** Supply an `Uploader` `(file, { file, signal, onProgress }) => Promise<void>` to switch to self-driving mode: the component runs the batch itself with `concurrency`, progress, the delay alert, abort and retry. Unset (default) the component only emits intents. */
@@ -3650,12 +4540,26 @@ declare class RelUpload extends LitElement {
3650
4540
  delayThreshold: number;
3651
4541
  private dragOver;
3652
4542
  private delayed;
4543
+ /** Names replaced by the latest pick; drives the info notice. */
4544
+ private replacedNames;
4545
+ /** Files in the last batch when it finished with no failures; drives the success alert. */
4546
+ private uploadedCount;
4547
+ /** Rows whose image preview failed to decode; they show the file icon. */
4548
+ private thumbFailed;
4549
+ /** Object URLs of image previews, by row id. Revoked as soon as the row leaves the list, and on disconnect. */
4550
+ private thumbUrls;
3653
4551
  private inputEl;
3654
4552
  private aborter?;
3655
4553
  private delayTimer?;
3656
4554
  private lastSignature;
3657
4555
  private lastHeading;
3658
4556
  private lastAlertText;
4557
+ /** Rows rejected by `max-files`: they hold no place until `retry()` re-checks them. */
4558
+ private overLimit;
4559
+ private batchIds;
4560
+ private wasUploading;
4561
+ private batchCancelled;
4562
+ private settledBatch?;
3659
4563
  static styles: lit.CSSResult;
3660
4564
  /**
3661
4565
  * Aggregate state. Read-only; corresponds to the Figma modal variants.
@@ -3673,6 +4577,12 @@ declare class RelUpload extends LitElement {
3673
4577
  * rather than re-queueing it blindly.
3674
4578
  */
3675
4579
  private get retryable();
4580
+ /** The `max-files` limit in force: none without `multiple` or at `0`. */
4581
+ private get fileLimit();
4582
+ /** Whether a row already in the list holds one of the `max-files` places:
4583
+ * any row that is not an error, and failed rows that a retry would send
4584
+ * again (not rejected by `accept`, `max-size` or the limit itself). */
4585
+ private holdsPlace;
3676
4586
  /** Whether the primary action is enabled: not disabled, nothing in flight, and either `ready` rows exist or (in the failed phase) at least one failed row can be retried. */
3677
4587
  get canUpload(): boolean;
3678
4588
  /** Footer button label — "Upload", "Upload 3 files" or "Retry upload". */
@@ -3719,6 +4629,16 @@ declare class RelUpload extends LitElement {
3719
4629
  private onDrop;
3720
4630
  connectedCallback(): void;
3721
4631
  disconnectedCallback(): void;
4632
+ /** Revoke the previews of rows no longer in `files`. */
4633
+ private pruneThumbs;
4634
+ /** An object URL for an image row's preview, created once per row. */
4635
+ private thumbUrl;
4636
+ /** A preview that cannot be decoded falls back to the file icon. */
4637
+ private onThumbError;
4638
+ /** Batch tracking for `rel-upload-complete`, from the `files` the update
4639
+ * renders. Self-driving (`patch()`) and controlled (the app writes
4640
+ * `files`) both end here, so one rule serves both modes. */
4641
+ willUpdate(changed: Map<PropertyKey, unknown>): void;
3722
4642
  updated(): void;
3723
4643
  private rowMeta;
3724
4644
  private renderRow;
@@ -3753,7 +4673,7 @@ declare global {
3753
4673
  * @csspart cancel - The secondary Cancel `rel-button` (absent when `no-cancel` is set)
3754
4674
  * @csspart primary - The primary Upload / Retry `rel-button`
3755
4675
  */
3756
- declare class RelUploadActions extends LitElement {
4676
+ declare class RelUploadActions extends RelElement {
3757
4677
  /** Id of the `<rel-upload>` to control. Omit to auto-discover a sibling. */
3758
4678
  for: string;
3759
4679
  /** Cancel button label. */
@@ -3825,6 +4745,7 @@ declare class FocusTrap {
3825
4745
  private getContainer;
3826
4746
  private previousFocus;
3827
4747
  private active;
4748
+ private generation;
3828
4749
  constructor(getContainer: () => Element | ShadowRoot | null);
3829
4750
  activate(options?: {
3830
4751
  initialFocus?: HTMLElement | null;
@@ -3883,6 +4804,69 @@ declare function bindShortcut(shortcut: string, handler: ShortcutHandler, option
3883
4804
  */
3884
4805
  declare function formatShortcutParts(shortcut: string): string[];
3885
4806
 
4807
+ /**
4808
+ * New in 1.5.0. Keyboard navigation for a two-dimensional grid of cells
4809
+ * (WAI-ARIA Grid pattern, data grid flavour), framework-free so that plain-JS
4810
+ * recipes can use it as well as components.
4811
+ *
4812
+ * - Roving tabindex: exactly one cell has `tabindex="0"`; a click or any other
4813
+ * focus on a cell moves the Tab stop there.
4814
+ * - Arrows move one cell (Left / Right swap in a right-to-left grid); Home / End
4815
+ * go to the row's first / last cell; Ctrl+Home / Ctrl+End (Cmd on a Mac) to the
4816
+ * grid's first / last cell; PageUp / PageDown move `pageSize` rows when set.
4817
+ * - Enter, Space and a click activate a cell (`onActivate`), except a disabled
4818
+ * one (`aria-disabled="true"`).
4819
+ *
4820
+ * Only the keys it handles are prevented. The grid's rows and cells are re-read
4821
+ * by `refresh()`: call it after re-rendering them.
4822
+ *
4823
+ * ```js
4824
+ * import { createGridNavigation } from "@reliability-design/web";
4825
+ * const nav = createGridNavigation(document.querySelector('[role="grid"]'), {
4826
+ * onActivate(cell, pos) { select(pos.row, pos.col) },
4827
+ * });
4828
+ * ```
4829
+ */
4830
+ /** A cell's place in the grid: zero-based row and column. */
4831
+ interface GridPosition {
4832
+ row: number;
4833
+ col: number;
4834
+ }
4835
+ interface GridNavigationOptions {
4836
+ /** Selector for the rows, inside the grid. Rows give the grid its shape. Default `'[role="row"]'`. */
4837
+ rowSelector?: string;
4838
+ /** Selector for the cells, inside a row, in DOM order. Default `'[role="gridcell"]'`. Rows with no cells (a header row of `columnheader`s) are not part of the grid. */
4839
+ cellSelector?: string;
4840
+ /** A disabled cell (`aria-disabled="true"`): `"focus"` (default) keeps it reachable, as the WAI-ARIA grid pattern does, but it does not activate; `"skip"` jumps over it. */
4841
+ disabledCells?: "focus" | "skip";
4842
+ /** Arrows past an edge continue at the opposite edge of the same row or column. Default `false`: they stop at the edge. */
4843
+ wrap?: boolean;
4844
+ /** PageUp / PageDown move this many rows (clamped to the grid). Unset: the Page keys are not handled. */
4845
+ pageSize?: number;
4846
+ /** `"auto"` (default) reads the grid's computed `direction` on every key, so Left / Right follow a right-to-left layout. */
4847
+ direction?: "auto" | "ltr" | "rtl";
4848
+ /** Enter, Space or a click on a cell that is not disabled. */
4849
+ onActivate?(cell: HTMLElement, pos: GridPosition, event: KeyboardEvent | MouseEvent): void;
4850
+ /** After a key moved the focus to `cell`. */
4851
+ onMove?(cell: HTMLElement, pos: GridPosition, event: KeyboardEvent): void;
4852
+ /** A custom key map, consulted first: return the target position, `null` to leave the key unhandled, or `undefined` to use the default map. */
4853
+ resolve?(key: string, pos: GridPosition, event: KeyboardEvent): GridPosition | null | undefined;
4854
+ }
4855
+ interface GridNavigation {
4856
+ /** The cell that holds the Tab stop, or `null` while the grid has no cells. */
4857
+ readonly active: GridPosition | null;
4858
+ /** Moves the Tab stop to `pos` (clamped to the grid) and focuses it, unless `focus: false`. */
4859
+ focusCell(pos: GridPosition, opts?: {
4860
+ focus?: boolean;
4861
+ }): void;
4862
+ /** Re-reads the rows and cells after a re-render; the Tab stop keeps its position, clamped. */
4863
+ refresh(): void;
4864
+ /** Removes the listeners. The cells keep their tabindex. */
4865
+ destroy(): void;
4866
+ }
4867
+ /** New in 1.5.0. Adds grid keyboard navigation and a roving Tab stop to `grid`. See the module comment. */
4868
+ declare function createGridNavigation(grid: HTMLElement, options?: GridNavigationOptions): GridNavigation;
4869
+
3886
4870
  /**
3887
4871
  * Shared screen-reader announcer.
3888
4872
  *
@@ -3904,4 +4888,4 @@ type Politeness = "polite" | "assertive";
3904
4888
  */
3905
4889
  declare function announce(message: string, politeness?: Politeness): void;
3906
4890
 
3907
- export { type AlertTone, type AlertToneAlias, type AlertToneInput, type AutocompleteFilter, type AutocompleteItem, type AutocompleteSearchFn, type AvatarSize, type AvatarSizeAlias, type AvatarSizeInput, type BadgeSize, type BadgeSizeAlias, type BadgeSizeInput, type BindShortcutOptions, type BreadcrumbItem, type ButtonTone, type ButtonVariant, type ChipBorder, type ChipSize, type ChipSizeAlias, type ChipSizeInput, type ChipTone, type ChipToneAlias, type ChipToneInput, type ChipVariant, type ChipVariantAlias, type ChipVariantInput, type ContainerBackground, type ContainerPadding, type ContainerPaddingAlias, type ContainerPaddingInput, type ContainerSurface, type DrawerCloseReason, type DrawerSide, type FieldLabelStyle, FocusTrap, type KbdSize, type LabelPlacement, type MenuItem, type MenuSize, type MenuType, type MenuWidth, type MenubarType, type ModalSide, type ModalSize, type ModalSizeAlias, type ModalSizeInput, type ModalVariant, type Orientation, type OtpInputMode, type OtpSize, type OtpType, type PopoverContentSize, type PopoverContentSizeAlias, type PopoverContentSizeInput, type ProgressMode, type RadioOption, type RadioSize, type RadioSizeAlias, type RadioSizeInput, type RangePickerSize, type RangePickerSizeAlias, type RangePickerSizeInput, RelAccordion, RelAlert, RelAutocomplete, RelAvatar, RelBadge, RelBreadcrumbs, RelButton, RelCalendar, RelCard, RelCardMedia, RelCarousel, RelCheckbox, RelChip, RelContainer, RelDatePicker, RelDivider, RelDrawer, RelDrawerFooter, RelKbd, RelKbdGroup, RelLayout, RelMenu, RelMenuItem, RelMenubar, RelMenubarMenu, RelModal, RelOtp, RelPagination, RelPopover, RelPopoverContent, RelProgress, RelRadio, RelRangePicker, RelResizableGroup, RelResizableHandle, RelResizablePanel, RelSegmentedButton, RelSelect, RelSkeleton, RelSlider, RelSnackbar, RelStepper, RelSwitch, RelTab, RelTabItem, RelTextfield, RelTimePicker, RelToast, RelTooltip, RelUpload, RelUploadActions, type SegmentAlign, type SegmentOption, type SegmentSize, type SelectBorder, type SelectLabelStyle, type SelectOption, type SelectOptionObject, type SelectSize, type SelectState, type ShortcutHandler, type SkeletonAnimation, type SkeletonShape, type SliderSize, type SliderSizeAlias, type SliderSizeInput, type SliderType, type SnackbarLines, type SnackbarTone, type SnackbarToneAlias, type SnackbarToneInput, type SnackbarType, type SnackbarVariant, type Step, type StepStatus, type StepperConnector, type StepperSize, type StepperType, type SwitchLabelPlacement, type SwitchSize, type SwitchSizeAlias, type SwitchSizeInput, type TabItem, type TabSize, type TextAlign, type TextFieldPrefixStyle, type TextFieldSize, type TextFieldState, type TextFieldVariant, type ToastPosition, type ToastSize, type ToastSizeAlias, type ToastSizeInput, type ToastTone, type ToastToneAlias, type ToastToneInput, type ToastType, type TooltipPosition, type TooltipTone, type TooltipType, type TrackStyle, type UploadFile, type UploadPhase, type UploadRejection, type UploadState, type UploadStatus, type Uploader, type UploaderContext, announce, bindShortcut, formatShortcutParts, getDeepActiveElement, getFocusableElements, isApplePlatform, isEditableTarget, matchesShortcut, parseShortcut, trapTabKey };
4891
+ export { type AlertTone, type AlertToneAlias, type AlertToneInput, type AutocompleteFilter, type AutocompleteItem, type AutocompleteSearchFn, type AvatarBadge, type AvatarSize, type AvatarSizeAlias, type AvatarSizeInput, type BadgeSize, type BadgeSizeAlias, type BadgeSizeInput, type BindShortcutOptions, type BreadcrumbItem, type ButtonTone, type ButtonVariant, type ChipBorder, type ChipSize, type ChipSizeAlias, type ChipSizeInput, type ChipTone, type ChipToneAlias, type ChipToneInput, type ChipVariant, type ChipVariantAlias, type ChipVariantInput, type ContainerBackground, type ContainerPadding, type ContainerPaddingAlias, type ContainerPaddingInput, type ContainerSurface, type DrawerCloseReason, type DrawerSide, type FieldLabelStyle, FocusTrap, type GridNavigation, type GridNavigationOptions, type GridPosition, type KbdPlatform, type KbdSize, type LabelPlacement, type MenuItem, type MenuSize, type MenuType, type MenuWidth, type MenubarType, type ModalSide, type ModalSize, type ModalSizeAlias, type ModalSizeInput, type ModalVariant, type Orientation, type OtpInputMode, type OtpSize, type OtpType, type PaginationType, type PopoverContentSize, type PopoverContentSizeAlias, type PopoverContentSizeInput, type ProgressMode, type RadioOption, type RadioSize, type RadioSizeAlias, type RadioSizeInput, type RangePickerSize, type RangePickerSizeAlias, type RangePickerSizeInput, RelAccordion, RelAlert, RelAutocomplete, RelAvatar, RelAvatarGroup, RelBadge, RelBreadcrumbs, RelButton, RelCalendar, RelCard, RelCardMedia, RelCarousel, RelCheckbox, RelChip, RelContainer, RelDatePicker, RelDivider, RelDrawer, RelDrawerFooter, RelKbd, RelKbdGroup, RelLayout, RelMenu, RelMenuItem, RelMenubar, RelMenubarMenu, RelModal, RelOtp, RelPagination, RelPopover, RelPopoverContent, RelProgress, RelRadio, RelRangePicker, RelResizableGroup, RelResizableHandle, RelResizablePanel, RelSegmentedButton, RelSelect, RelSignaturePad, RelSkeleton, RelSlider, RelSnackbar, RelSplitButton, RelStepper, RelSwitch, RelTab, RelTabItem, RelTextfield, RelTimePicker, RelToast, RelTooltip, RelTree, RelUpload, RelUploadActions, type SegmentAlign, type SegmentOption, type SegmentSize, type SelectBorder, type SelectLabelStyle, type SelectOption, type SelectOptionObject, type SelectSize, type SelectState, type ShortcutHandler, type SignaturePadChangeDetail, type SignaturePadMode, type SignaturePoint, type SignatureStroke, type SkeletonAnimation, type SkeletonShape, type SliderSize, type SliderSizeAlias, type SliderSizeInput, type SliderType, type SnackbarLines, type SnackbarTone, type SnackbarToneAlias, type SnackbarToneInput, type SnackbarType, type SnackbarVariant, type SplitButtonItem, type SplitButtonSize, type SplitButtonTone, type SplitButtonVariant, type Step, type StepStatus, type StepperConnector, type StepperSize, type StepperType, type SwitchLabelPlacement, type SwitchSize, type SwitchSizeAlias, type SwitchSizeInput, type TabIconPlacement, type TabItem, type TabOrientation, type TabSize, type TextAlign, type TextFieldPrefixStyle, type TextFieldSize, type TextFieldState, type TextFieldVariant, type ToastPosition, type ToastSize, type ToastSizeAlias, type ToastSizeInput, type ToastTone, type ToastToneAlias, type ToastToneInput, type ToastType, type TooltipPosition, type TooltipTone, type TooltipType, type TrackStyle, type TreeItem, type TreeSelection, type UploadFile, type UploadPhase, type UploadRejection, type UploadState, type UploadStatus, type Uploader, type UploaderContext, announce, bindShortcut, createGridNavigation, detectKbdPlatform, formatShortcutParts, getDeepActiveElement, getFocusableElements, isApplePlatform, isEditableTarget, matchesShortcut, parseShortcut, resolveKbdKey, trapTabKey };