@reliability-design/web 1.4.7 → 1.4.8

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 (46) hide show
  1. package/CHANGELOG.md +37 -2
  2. package/dist/components/rel-accordion/README.md +2 -1
  3. package/dist/components/rel-autocomplete/README.md +21 -5
  4. package/dist/components/rel-avatar/README.md +9 -7
  5. package/dist/components/rel-badge/README.md +4 -5
  6. package/dist/components/rel-button/README.md +32 -2
  7. package/dist/components/rel-card/README.md +3 -3
  8. package/dist/components/rel-carousel/README.md +2 -0
  9. package/dist/components/rel-checkbox/README.md +7 -4
  10. package/dist/components/rel-chip/README.md +13 -7
  11. package/dist/components/rel-container/README.md +8 -8
  12. package/dist/components/rel-date-picker/README.md +1 -1
  13. package/dist/components/rel-divider/README.md +11 -1
  14. package/dist/components/rel-kbd/README.md +2 -2
  15. package/dist/components/rel-layout/README.md +34 -0
  16. package/dist/components/rel-menu/README.md +3 -1
  17. package/dist/components/rel-menubar/README.md +1 -1
  18. package/dist/components/rel-otp/README.md +4 -3
  19. package/dist/components/rel-pagination/README.md +2 -2
  20. package/dist/components/rel-popover/README.md +5 -5
  21. package/dist/components/rel-progress/README.md +3 -3
  22. package/dist/components/rel-radio/README.md +1 -1
  23. package/dist/components/rel-range-picker/README.md +4 -1
  24. package/dist/components/rel-segmented-button/README.md +2 -1
  25. package/dist/components/rel-select/README.md +5 -3
  26. package/dist/components/rel-sheet/README.md +3 -3
  27. package/dist/components/rel-skeleton/README.md +5 -4
  28. package/dist/components/rel-slider/README.md +3 -2
  29. package/dist/components/rel-snackbar/README.md +13 -7
  30. package/dist/components/rel-stepper/README.md +11 -7
  31. package/dist/components/rel-switch/README.md +16 -7
  32. package/dist/components/rel-tab/README.md +4 -2
  33. package/dist/components/rel-textfield/README.md +18 -1
  34. package/dist/components/rel-toast/README.md +14 -6
  35. package/dist/components/rel-tooltip/README.md +13 -2
  36. package/dist/components/rel-upload/README.md +2 -2
  37. package/dist/index.d.mts +709 -237
  38. package/dist/index.d.ts +709 -237
  39. package/dist/index.js +10397 -7840
  40. package/dist/index.js.map +1 -1
  41. package/dist/index.mjs +7458 -4901
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/styles/focus-ring.ts +84 -0
  44. package/dist/styles/vars.css +75 -60
  45. package/dist/{vars-JHTUGNRG.css → vars-DXX6QOAG.css} +75 -60
  46. package/package.json +1 -1
package/dist/index.d.mts CHANGED
@@ -26,10 +26,22 @@ declare class RelAccordion extends LitElement {
26
26
  * `md` = large (the default), `sm` = medium.
27
27
  */
28
28
  size: "md" | "sm" | "large" | "medium" | "small";
29
- /** 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. */
29
+ /**
30
+ * 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
+ */
30
40
  type: "default" | "action";
31
41
  /** 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. */
32
42
  appearance: "shadow" | "plain";
43
+ /** 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. */
44
+ disabled: boolean;
33
45
  static styles: lit.CSSResult;
34
46
  /** Stable id prefix wiring the header to its panel. */
35
47
  private accordionId;
@@ -112,7 +124,9 @@ type FieldLabelStyle = "default" | "floating" | "none";
112
124
  * @csspart suffix - The suffix block (suffix text, trailing icon and the `suffix` slot)
113
125
  * @csspart right-icon - The trailing `rel-icon`
114
126
  * @csspart helper - The helper / error text line below the control
115
- * @cssprop --rel-field-label-reserve - Height held for the absent label row under `labelStyle="none"` so the control bottom-aligns with labelled siblings; defaults to the label line-height plus the wrapper gap. Set to `0` to collapse the row entirely (toolbars, table cells).
127
+ * @csspart helper-icon - The leading `rel-icon` in the helper line; rendered only when `helper-icon` is set and there is helper text
128
+ * @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
+ * @cssprop --rel-textfield-surface - Colour the floating label's strip paints to mask the border behind it; defaults to the control's own fill (`--rel-bg-neutral`). Set it when the field sits on a tinted or elevated surface, otherwise the strip shows as a light dash across the border.
116
130
  */
117
131
  declare class RelTextfield extends LitElement {
118
132
  /**
@@ -193,6 +207,8 @@ declare class RelTextfield extends LitElement {
193
207
  errorMessage?: string;
194
208
  /** Helper line below the control (preferred over the legacy `supportingText`); linked to the input via `aria-describedby`. */
195
209
  helperText?: string;
210
+ /** Icon name from the generated icon dictionary, rendered before the helper line (Figma "Supporting Icon"). Sized 16px, or 14px at `size="small"`, and it takes the helper's colour — so it turns `--rel-text-danger` in the error state. Decorative: it is hidden from screen readers, since the helper text it sits beside is already announced. */
211
+ helperIcon?: string;
196
212
  /** Makes the input read-only: focusable and selectable but not editable. */
197
213
  readonly: boolean;
198
214
  /** Maximum number of characters (native `maxlength`). */
@@ -248,6 +264,185 @@ declare class RelTextfield extends LitElement {
248
264
  render(): lit_html.TemplateResult<1>;
249
265
  }
250
266
 
267
+ type CheckboxState = "default" | "error" | "disabled";
268
+ type CheckboxStatus = "unchecked" | "checked" | "indeterminate";
269
+ type CheckboxSize = "small" | "medium" | "large" | "extra-large";
270
+ type CheckboxDensity = "compact" | "comfortable";
271
+ type CheckboxAlignment = "leading" | "trailing";
272
+ /**
273
+ * Tri-state checkbox (Figma "Checkbox"): a hidden native checkbox behind a custom box, with label, optional prefix icon and supporting text.
274
+ * @csspart base - The outer wrapper that carries the `state` / `status` classes
275
+ * @csspart label - The visible label text
276
+ * @csspart helper - The supporting text (rendered beside the box in comfortable density, under the row in compact)
277
+ */
278
+ declare class RelCheckbox extends LitElement {
279
+ /** Label text rendered next to the box. */
280
+ label: string;
281
+ /** rel-icon name rendered between the checkbox and the label (label prefix icon); scales with `size`. Unset renders no icon and reserves no space. */
282
+ icon: string;
283
+ /** Supporting text under or beside the label; where it renders depends on `density`. */
284
+ supportingtext: string;
285
+ /** 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
+ state: CheckboxState;
287
+ /** Check state. `"indeterminate"` is programmatic-only — a user toggle always resolves to checked or unchecked. */
288
+ status: CheckboxStatus;
289
+ /** Value reported in the `rel-change` event detail (it is not submitted with a form). */
290
+ value: string;
291
+ /** Control scale (Figma Size), on Figma's four-step ramp of control frames:
292
+ * `"small"` 16px / body-sm, `"medium"` (default) 18px / body-md,
293
+ * `"large"` 20px / body-lg, `"extra-large"` 24px / body-xl. The drawn
294
+ * square is inset to 80% of the frame, so the frame is the layout
295
+ * footprint (what the row gap and the compact indent measure from) rather
296
+ * than the visible square. */
297
+ size: CheckboxSize;
298
+ /** Supporting-text layout (Figma Density). `"compact"` (default) puts the
299
+ * supporting text on its own line under the row, indented past the box;
300
+ * `"comfortable"` stacks label + supporting text beside the box. */
301
+ density: CheckboxDensity;
302
+ /** Box position (Figma Alignment). `"leading"` (default) renders the box
303
+ * before the label; `"trailing"` pushes it to the far edge of the row.
304
+ * Trailing has no compact layout in the spec, so it always renders with
305
+ * comfortable density. */
306
+ alignment: CheckboxAlignment;
307
+ /** Boolean view of `status`: `true` when `status === "checked"`; setting it writes `"checked"` / `"unchecked"`. */
308
+ get checked(): boolean;
309
+ set checked(val: boolean);
310
+ private _onChange;
311
+ private _onTouched;
312
+ static styles: lit.CSSResult;
313
+ protected willUpdate(changed: PropertyValues<this>): void;
314
+ /** Trailing has no compact layout in the spec — trailing wins. */
315
+ private get effectiveDensity();
316
+ private toggle;
317
+ /** @internal Angular ControlValueAccessor bridge. */
318
+ registerOnChange(fn: (value: boolean) => void): void;
319
+ /** @internal Angular ControlValueAccessor bridge. */
320
+ registerOnTouched(fn: () => void): void;
321
+ /** @internal Angular ControlValueAccessor bridge. */
322
+ writeValue(checked: boolean): void;
323
+ render(): lit_html.TemplateResult<1>;
324
+ }
325
+
326
+ /** Diameter step, spelled out. */
327
+ type AvatarSize = "large" | "medium" | "small" | "extra-small";
328
+ /** Alternative spellings, not deprecations — the short names keep working. */
329
+ type AvatarSizeAlias = "lg" | "md" | "sm" | "xs";
330
+ type AvatarSizeInput = AvatarSize | AvatarSizeAlias;
331
+ /**
332
+ * Circular avatar (Figma "Avatar"): shows a photo, text initials or a slotted icon in one of four sizes and three fill variants.
333
+ * @slot - Icon content rendered when `type="icon"` (typically an inline SVG, sized 20 / 18 / 16 / 12px by `size`)
334
+ * @csspart base - The circular wrapper that carries the size, variant and inline `bg` / `color` overrides
335
+ * @csspart icon - The `<img>` (`type="image"`) or the icon wrapper span around the slot (`type="icon"`)
336
+ * @csspart label - The `<span>` holding the initials when `type="text"`
337
+ * @cssprop --rel-avatar-bg-fill - Background colour of the `fill` variant
338
+ * @cssprop --rel-avatar-color-fill - Text / icon colour of the `fill` variant
339
+ * @cssprop --rel-avatar-bg-subtle - Background colour of the `subtle` variant
340
+ * @cssprop --rel-avatar-color-subtle - Text / icon colour of the `subtle` variant
341
+ * @cssprop --rel-avatar-border-outline - 1px border colour of the `outline` variant
342
+ * @cssprop --rel-avatar-color-outline - Text / icon colour of the `outline` variant
343
+ * @cssprop --rel-avatar-icon-size - Overrides the slotted icon's size; defaults to the preset ramp (20 / 18 / 16 / 12px)
344
+ * @cssprop --rel-avatar-bg-outline - Background colour of the `outline` variant; defaults to --rel-bg-neutral
345
+ * @cssprop --rel-avatar-size - Overrides the circle's width/height for sizes between (or below) the `size` presets; defaults to the preset's diameter
346
+ * @cssprop --rel-avatar-font-size - Overrides the initials' font-size, for use alongside `--rel-avatar-size`; defaults to the preset's text role
347
+ */
348
+ declare class RelAvatar extends LitElement {
349
+ /** 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. */
350
+ size: AvatarSizeInput;
351
+ /** What is rendered inside the circle: `image` shows `src`, `text` shows the `text` initials, `icon` shows the default slot. */
352
+ type: "image" | "text" | "icon";
353
+ /** Fill style: `fill` = solid inverse background with light text, `subtle` = elevated neutral background, `outline` = transparent with a 1px border. */
354
+ variant: "fill" | "subtle" | "outline";
355
+ /** Image URL for `type="image"`; when empty nothing is rendered inside the circle (there is no fallback to initials). */
356
+ src: string;
357
+ /** Initials shown for `type="text"`; rendered uppercase via `text-transform`. */
358
+ text: string;
359
+ /** Alt text of the `<img>` for `type="image"`; the default is the generic word "Avatar", so pass the person's name. */
360
+ alt: string;
361
+ /** Any CSS colour applied inline as `background-color` on the circle, overriding the variant background. */
362
+ bg: string;
363
+ /** Any CSS colour applied inline as `color` on the circle (initials / icon), overriding the variant text colour. */
364
+ color: string;
365
+ static styles: lit.CSSResult;
366
+ /** Alternative spellings, NOT deprecations — no console notice. Normalising
367
+ * the property rather than duplicating every selector keeps the reflected
368
+ * attribute and the rendered class canonical, so only one spelling reaches
369
+ * the CSS. Same shape as rel-chip and rel-slider. */
370
+ protected willUpdate(changed: Map<string, unknown>): void;
371
+ render(): lit_html.TemplateResult<1>;
372
+ }
373
+
374
+ /** Canonical semantic tones (Figma "Tone"): neutral | destructive | warning | success | info. */
375
+ type ChipTone = "neutral" | "destructive" | "warning" | "success" | "info";
376
+ /** Deprecated spelling: "danger" → destructive. */
377
+ type ChipToneAlias = "danger";
378
+ type ChipToneInput = ChipTone | ChipToneAlias;
379
+ /** Canonical emphasis (Figma "Style"): fill | subtle. */
380
+ type ChipVariant = "fill" | "subtle";
381
+ /** Figma's own spellings of the Style axis, accepted as alternatives. */
382
+ type ChipVariantAlias = "primary" | "secondary";
383
+ type ChipVariantInput = ChipVariant | ChipVariantAlias;
384
+ /** Canonical size steps — full words, as elsewhere in the library. */
385
+ type ChipSize = "small" | "medium" | "large";
386
+ /** Short size spellings, accepted as alternatives. */
387
+ type ChipSizeAlias = "sm" | "md" | "lg";
388
+ type ChipSizeInput = ChipSize | ChipSizeAlias;
389
+ type ChipBorder = "default" | "rounded";
390
+ /**
391
+ * Compact chip (Figma "Chip") for tags, filters and selected values: tone x variant
392
+ * colouring, optional leading icon / avatar / trailing icon, a remove control and
393
+ * opt-in drag-and-drop.
394
+ * @csspart base - The chip body (`.chip`) that carries size, tone, variant and border styling
395
+ * @csspart icon - The leading `<rel-icon>` (rendered only when `leading-icon` is set and no avatar)
396
+ * @csspart label - The label text
397
+ * @cssprop --rel-chip-focus-ring - Colour of the keyboard focus outline; default `var(--rel-border-focus)`
398
+ */
399
+ declare class RelChip extends LitElement {
400
+ static shadowRootOptions: ShadowRootInit;
401
+ /** Text shown in the chip; also its accessible name and the drag payload. */
402
+ label: string;
403
+ /** 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. */
404
+ tone: ChipToneInput;
405
+ /** 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`. */
406
+ variant: ChipVariantInput;
407
+ /** Height scale (Figma Size): `small` 24px / body-sm, `medium` 28px / body-md, `large` 32px / body-lg. Icon size follows (12 / 14 / 16px). `sm` / `md` / `lg` are accepted and normalised to the full words. */
408
+ size: ChipSizeInput;
409
+ /** Corner shape (Figma Border): `default` = 4px radius, `rounded` = fully rounded with 12px horizontal padding. */
410
+ border: ChipBorder;
411
+ /** Icon name from the generated icon dictionary (e.g. "settings") — rendered via <rel-icon> before the label. Ignored when `avatar` is set. */
412
+ leadingIcon?: string;
413
+ /** Icon name from the generated icon dictionary, rendered after the label. */
414
+ trailingIcon?: string;
415
+ /** Initials text for the avatar (e.g. "JD"), rendered as a `<rel-avatar type="text">` before the label. Mutually exclusive with leadingIcon (avatar wins). */
416
+ avatar?: string;
417
+ /** Shows a remove (close) button after the label; clicking it sets `removed` and fires `removed`. */
418
+ removable: boolean;
419
+ /** `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. */
420
+ removed: boolean;
421
+ /** 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`. */
422
+ draggableChip: boolean;
423
+ /** Reflected so CSS can target the Figma "Dragged" visual state. Driven automatically by dragstart/dragend when draggableChip is set. */
424
+ private dragging;
425
+ static styles: lit.CSSResult[];
426
+ private get classes();
427
+ private handleRemove;
428
+ /** The chip body is announced as a button, so it must also act on keys:
429
+ * Enter/Space re-dispatch as a host click (consumers' @click handlers
430
+ * fire), Delete/Backspace remove when removable, and arrow keys ask the
431
+ * container to reorder a draggable chip (`chip-move`) — the keyboard
432
+ * alternative to pointer drag-and-drop. */
433
+ private handleKeydown;
434
+ private onDragStart;
435
+ private onDragEnd;
436
+ protected willUpdate(changed: Map<string, unknown>): void;
437
+ protected updated(changed: Map<string, unknown>): void;
438
+ render(): lit_html.TemplateResult<1>;
439
+ }
440
+ declare global {
441
+ interface HTMLElementTagNameMap {
442
+ "rel-chip": RelChip;
443
+ }
444
+ }
445
+
251
446
  /**
252
447
  * An item is a plain string or any object read through displayField/valueField.
253
448
  * Two keys are read off objects directly, whatever those fields are named:
@@ -272,7 +467,9 @@ type AutocompleteSearchFn = (query: string) => Promise<AutocompleteItem[]>;
272
467
  * item's label/value on Enter/blur).
273
468
  * @csspart panel - The suggestion listbox container (same node as `list`)
274
469
  * @csspart list - Alias of `panel`
275
- * @csspart option - A suggestion row
470
+ * @csspart option - A suggestion row. Emitted in BOTH single and `multiple` mode, unlike the two parts below
471
+ * @csspart option-label - The row's first line (the matched label). SINGLE-SELECT ROWS ONLY
472
+ * @csspart option-supporting-text - The row's optional second line, rendered only when the item supplies one. SINGLE-SELECT ROWS ONLY — under `multiple` the row is a nested `rel-checkbox` and the second line is that component's own `helper` part, inside its shadow root with nothing forwarding it out, so it cannot be reached with `::part()` at all. Style both modes with the `--rel-*` tokens instead
276
473
  * @csspart label - The inner field's label (re-exported from `rel-textfield`)
277
474
  * @csspart control - The inner field's bordered control box (re-exported from `rel-textfield`)
278
475
  * @csspart input - The inner native input (re-exported from `rel-textfield`)
@@ -288,9 +485,10 @@ type AutocompleteSearchFn = (query: string) => Promise<AutocompleteItem[]>;
288
485
  * @cssprop --rel-autocomplete-value-color - Suggestion label colour
289
486
  * @cssprop --rel-autocomplete-icon-color - Colour of a row's leading `icon`
290
487
  * @cssprop --rel-autocomplete-status-color - Colour of the loading / no-results status rows
488
+ * @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.
291
489
  */
292
490
  declare class RelAutocomplete extends LitElement {
293
- /** Local (or fallback initial) suggestion list. Ignored while searchFn drives results. */
491
+ /** 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. */
294
492
  items: AutocompleteItem[];
295
493
  /** Committed value — the selected item's valueField string ("" = none). */
296
494
  value: string;
@@ -298,6 +496,8 @@ declare class RelAutocomplete extends LitElement {
298
496
  displayField: string;
299
497
  /** Object key read for the committed value when items are objects. */
300
498
  valueField: string;
499
+ /** Object key read for the row's optional second line. Defaults to `supportingText`, the fixed key `rel-select` and `rel-radio` already use; point it elsewhere when your records cannot be reshaped. */
500
+ supportingField: string;
301
501
  /** Local filtering mode. Ignored when searchFn is set (results arrive pre-filtered). */
302
502
  filter: AutocompleteFilter;
303
503
  /** Async data source. When set, local filtering is bypassed entirely. */
@@ -308,6 +508,24 @@ declare class RelAutocomplete extends LitElement {
308
508
  minSearchLength: number;
309
509
  /** Optional cap on rendered suggestions (panel scrolls regardless). */
310
510
  maxSuggestions: number;
511
+ /**
512
+ * Multi-select mode: rows become checkboxes with a Select-all row, and the
513
+ * committed values live in `selectedValues` rather than `value`. Mirrors
514
+ * `rel-select`'s `multiple` rather than introducing a second pattern.
515
+ *
516
+ * THE TEXT INPUT STAYS A SEARCH BOX. In single mode the input shows the
517
+ * selected label, which is the point of a type-ahead; in multi mode there is
518
+ * no single label to show, and writing a value list into the field would
519
+ * destroy the query the user is typing. `rel-select` can put chips inside its
520
+ * trigger because a trigger is not an editable surface — an input is. So the
521
+ * chips render beneath the field, the query clears after each toggle so the
522
+ * next search starts fresh, and the panel stays open.
523
+ */
524
+ multiple: boolean;
525
+ /** Committed values in `multiple` mode (ignored otherwise). Mirrors `rel-select.selectedValues`. */
526
+ selectedValues: string[];
527
+ /** Label of the Select-all row shown at the top of the panel in `multiple` mode. */
528
+ selectAllLabel: string;
311
529
  /** Shows a trailing ✕ that clears the text and the committed value. */
312
530
  clearable: boolean;
313
531
  /** Externally-controlled loading state (internal searchFn loading is automatic). */
@@ -387,6 +605,14 @@ declare class RelAutocomplete extends LitElement {
387
605
  private openPanel;
388
606
  private closePanel;
389
607
  private showNativePopover;
608
+ /**
609
+ * Gap between the field and the panel. Figma composes the two in a column
610
+ * with gap=--rel-spacing-2xs, so this is a bound token rather than a magic
611
+ * number; the position is computed in JS (the panel is in the top layer), so
612
+ * it is read from the resolved custom property instead of being hardcoded.
613
+ * That way a spacing re-export carries through here as it does in CSS.
614
+ */
615
+ private get panelOffset();
390
616
  private hideNativePopover;
391
617
  private updatePosition;
392
618
  /** Re-evaluates whether the panel should be showing for the current state. */
@@ -394,6 +620,17 @@ declare class RelAutocomplete extends LitElement {
394
620
  private onFieldInput;
395
621
  private announceResults;
396
622
  private runSearch;
623
+ /**
624
+ * Multi rows nest the checkbox at the SAME step as the field, not one below.
625
+ * rel-select records why (rel-select.ts, optionCheckboxSize): the old
626
+ * one-step-down compensated for rel-checkbox sitting a size above the system,
627
+ * and once that ramp was realigned to Figma the compensation became the bug.
628
+ */
629
+ private get optionCheckboxSize();
630
+ private get selectAllStatus();
631
+ private toggleMultiOption;
632
+ private toggleSelectAll;
633
+ private emitMultiChanged;
397
634
  private selectItem;
398
635
  /** Silent commit when typed text exactly matches an item (label or value, case-insensitive). */
399
636
  private commitExactMatch;
@@ -417,47 +654,17 @@ declare global {
417
654
  }
418
655
  }
419
656
 
420
- /**
421
- * Circular avatar (Figma "Avatar"): shows a photo, text initials or a slotted icon in one of four sizes and three fill variants.
422
- * @slot - Icon content rendered when `type="icon"` (typically an inline SVG, sized to 50% of the circle)
423
- * @csspart base - The circular wrapper that carries the size, variant and inline `bg` / `color` overrides
424
- * @csspart icon - The `<img>` (`type="image"`) or the icon wrapper span around the slot (`type="icon"`)
425
- * @csspart label - The `<span>` holding the initials when `type="text"`
426
- * @cssprop --rel-avatar-bg-fill - Background colour of the `fill` variant
427
- * @cssprop --rel-avatar-color-fill - Text / icon colour of the `fill` variant
428
- * @cssprop --rel-avatar-bg-subtle - Background colour of the `subtle` variant
429
- * @cssprop --rel-avatar-color-subtle - Text / icon colour of the `subtle` variant
430
- * @cssprop --rel-avatar-border-outline - 1px border colour of the `outline` variant
431
- * @cssprop --rel-avatar-color-outline - Text / icon colour of the `outline` variant
432
- * @cssprop --rel-avatar-size - Overrides the circle's width/height for sizes between (or below) the `size` presets; defaults to the preset's diameter
433
- * @cssprop --rel-avatar-font-size - Overrides the initials' font-size, for use alongside `--rel-avatar-size`; defaults to the preset's text role
434
- */
435
- declare class RelAvatar extends LitElement {
436
- /** Diameter step: `lg` 40px, `md` 32px, `sm` 24px, `xs` 20px; each binds the matching body-*-semibold text role for initials. */
437
- size: "lg" | "md" | "sm" | "xs";
438
- /** What is rendered inside the circle: `image` shows `src`, `text` shows the `text` initials, `icon` shows the default slot. */
439
- type: "image" | "text" | "icon";
440
- /** Fill style: `fill` = solid inverse background with light text, `subtle` = elevated neutral background, `outline` = transparent with a 1px border. */
441
- variant: "fill" | "subtle" | "outline";
442
- /** Image URL for `type="image"`; when empty nothing is rendered inside the circle (there is no fallback to initials). */
443
- src: string;
444
- /** Initials shown for `type="text"`; rendered uppercase via `text-transform`. */
445
- text: string;
446
- /** Alt text of the `<img>` for `type="image"`; the default is the generic word "Avatar", so pass the person's name. */
447
- alt: string;
448
- /** Any CSS colour applied inline as `background-color` on the circle, overriding the variant background. */
449
- bg: string;
450
- /** Any CSS colour applied inline as `color` on the circle (initials / icon), overriding the variant text colour. */
451
- color: string;
452
- static styles: lit.CSSResult;
453
- render(): lit_html.TemplateResult<1>;
454
- }
455
-
657
+ /** Canonical size steps — full words, as elsewhere in the library. */
658
+ type BadgeSize = "small" | "medium" | "large" | "extra-large";
659
+ /** Short size spellings, accepted as alternatives. */
660
+ type BadgeSizeAlias = "sm" | "md" | "lg" | "xl";
661
+ type BadgeSizeInput = BadgeSize | BadgeSizeAlias;
456
662
  /**
457
663
  * Notification badge (Figma "Badge"): a small count pill or an unlabeled dot, usually positioned over an icon or avatar.
458
664
  * @csspart base - The `role="status"` pill / dot that carries the size, variant and colour overrides
459
665
  * @cssprop --rel-badge-bg - Background colour of the badge; the `color` prop writes this on the host
460
666
  * @cssprop --rel-badge-color - Text colour of the count; the `text-color` prop writes this on the host
667
+ * @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.
461
668
  */
462
669
  declare class RelBadge extends LitElement {
463
670
  /** 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. */
@@ -466,9 +673,9 @@ declare class RelBadge extends LitElement {
466
673
  ariaLabel: string;
467
674
  /** Noun appended to the count in the generated accessible name, e.g. "3 notifications". */
468
675
  suffix: string;
469
- /** Badge scale (Figma Badge S/M/L/XL): number badges are 20 / 24 / 28 / 40px tall, dot badges 8 / 10 / 12 / 12px. */
470
- size: "sm" | "md" | "lg" | "xl";
471
- /** `number` renders the `number` text in a pill that widens with its content; `dot` renders a small disc with no text. */
676
+ /** Badge scale (Figma Badge S/M/L/XL): number badges are 20 / 24 / 28 / 40px tall. `sm` / `md` / `lg` / `xl` are accepted and normalised to the full words. Ignored when `variant="dot"` — Figma models `Dot` as a fifth option of the same Size axis, so a dot has one size. */
677
+ size: BadgeSizeInput;
678
+ /** `number` renders the `number` text in a pill that widens with its content; `dot` renders a 6px disc with no text (Figma `Size=Dot`), ignoring `size`. */
472
679
  variant: "dot" | "number";
473
680
  /**
474
681
  * Direct CSS color override for the badge background. Accepts any valid
@@ -485,6 +692,7 @@ declare class RelBadge extends LitElement {
485
692
  */
486
693
  textColor: string;
487
694
  static styles: lit.CSSResult;
695
+ protected willUpdate(changed: PropertyValues<this>): void;
488
696
  private get classes();
489
697
  private get computedAriaLabel();
490
698
  updated(changedProperties: Map<PropertyKey, unknown>): void;
@@ -534,8 +742,14 @@ type ButtonTone = "default" | "primary" | "secondary" | "critical" | "success" |
534
742
  declare class RelButton extends LitElement {
535
743
  /** 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). */
536
744
  variant: ButtonVariant;
537
- /** Tone axis (Figma "Tone"). Canonical: `neutral` (= `default`), `brand` (= `primary`), `destructive`. Deprecated aliases `critical` and `danger` map to destructive and log a one-time console notice. `secondary` and `success` are legacy extras with no Figma counterpart (secondary only affects the legacy `icon` variant). */
538
- tone: ButtonTone;
745
+ /**
746
+ * Tone axis (Figma "Tone"). Canonical: `neutral` (= `default`), `brand` (= `primary`), `destructive`. Deprecated aliases `critical` and `danger` map to destructive and log a one-time console notice. `secondary` and `success` are legacy extras with no Figma counterpart (secondary only affects the legacy `icon` variant).
747
+ *
748
+ * Left UNSET it means `neutral`, except on `variant="primary"`, where it
749
+ * still means `brand` for one release — see `effectiveTone`. Write the tone
750
+ * explicitly and you always get what you wrote.
751
+ */
752
+ tone?: ButtonTone;
539
753
  /** Figma "Type=Icon Only": renders a square button (32/40/48px per `size`) of the current style and tone with no padding or min-width. Composes with every `variant`; slot a single `rel-icon` and set `aria-label`. */
540
754
  iconOnly: boolean;
541
755
  /** Figma "State=Loading": replaces the label with a spinner (`indicator` part), sets `aria-busy`, and blocks pointer and programmatic clicks. The button keeps its width. */
@@ -552,7 +766,7 @@ declare class RelButton extends LitElement {
552
766
  badge: string;
553
767
  /** Renders a small dot badge in the top-right corner instead of a numbered badge; takes precedence over `badge`. The dot is inset 2px from the corner so it lands on a circular icon button's edge. */
554
768
  badgeDot: boolean;
555
- static styles: lit.CSSResult;
769
+ static styles: lit.CSSResult[];
556
770
  connectedCallback(): void;
557
771
  disconnectedCallback(): void;
558
772
  private handleHostClick;
@@ -622,11 +836,11 @@ declare class RelCalendar extends LitElement {
622
836
  }
623
837
 
624
838
  /**
625
- * `<rel-card>` — bordered surface that groups related content (Figma "Card").
839
+ * `<rel-card>` — elevated surface that groups related content (Figma "Card").
626
840
  * Use `type="media"` with a `<rel-card-media>` in the `media` slot for a flush image header.
627
841
  * @slot - Card body; slotted headings and paragraphs receive the Figma card typography
628
842
  * @slot media - Flush media region above the body; rendered only when `type="media"`
629
- * @csspart base - The outer bordered card surface
843
+ * @csspart base - The outer elevated card surface
630
844
  * @csspart header - The media region wrapper (present only when `type="media"`)
631
845
  * @csspart content - The padded body that wraps the default slot
632
846
  */
@@ -786,63 +1000,6 @@ declare class RelCardMedia extends LitElement {
786
1000
  render(): lit_html.TemplateResult<1>;
787
1001
  }
788
1002
 
789
- type CheckboxState = "default" | "error" | "disabled";
790
- type CheckboxStatus = "unchecked" | "checked" | "indeterminate";
791
- type CheckboxSize = "small" | "medium" | "large";
792
- type CheckboxDensity = "compact" | "comfortable";
793
- type CheckboxAlignment = "leading" | "trailing";
794
- /**
795
- * Tri-state checkbox (Figma "Checkbox"): a hidden native checkbox behind a custom box, with label, optional prefix icon and supporting text.
796
- * @csspart base - The outer wrapper that carries the `state` / `status` classes
797
- * @csspart label - The visible label text
798
- * @csspart helper - The supporting text (rendered beside the box in comfortable density, under the row in compact)
799
- */
800
- declare class RelCheckbox extends LitElement {
801
- /** Label text rendered next to the box. */
802
- label: string;
803
- /** rel-icon name rendered between the checkbox and the label (label prefix icon); scales with `size`. Unset renders no icon and reserves no space. */
804
- icon: string;
805
- /** Supporting text under or beside the label; where it renders depends on `density`. */
806
- supportingtext: string;
807
- /** Visual and interaction state (Figma State): `"error"` swaps in the danger colours, `"disabled"` blocks input and dims the control to 0.55 opacity. */
808
- state: CheckboxState;
809
- /** Check state. `"indeterminate"` is programmatic-only — a user toggle always resolves to checked or unchecked. */
810
- status: CheckboxStatus;
811
- /** Value reported in the `rel-change` event detail (it is not submitted with a form). */
812
- value: string;
813
- /** Control scale (Figma Size). `"medium"` (default) = 18px box with body-lg
814
- * label; `"small"` = 14px box with body-md label; `"large"` (Figma L,
815
- * added 2026-09-04) = 20px box with body-lg label and body-sm supporting
816
- * text — the same additive step rel-radio took for its large size. */
817
- size: CheckboxSize;
818
- /** Supporting-text layout (Figma Density). `"compact"` (default) puts the
819
- * supporting text on its own line under the row, indented past the box;
820
- * `"comfortable"` stacks label + supporting text beside the box. */
821
- density: CheckboxDensity;
822
- /** Box position (Figma Alignment). `"leading"` (default) renders the box
823
- * before the label; `"trailing"` pushes it to the far edge of the row.
824
- * Trailing has no compact layout in the spec, so it always renders with
825
- * comfortable density. */
826
- alignment: CheckboxAlignment;
827
- /** Boolean view of `status`: `true` when `status === "checked"`; setting it writes `"checked"` / `"unchecked"`. */
828
- get checked(): boolean;
829
- set checked(val: boolean);
830
- private _onChange;
831
- private _onTouched;
832
- static styles: lit.CSSResult;
833
- protected willUpdate(changed: PropertyValues<this>): void;
834
- /** Trailing has no compact layout in the spec — trailing wins. */
835
- private get effectiveDensity();
836
- private toggle;
837
- /** @internal Angular ControlValueAccessor bridge. */
838
- registerOnChange(fn: (value: boolean) => void): void;
839
- /** @internal Angular ControlValueAccessor bridge. */
840
- registerOnTouched(fn: () => void): void;
841
- /** @internal Angular ControlValueAccessor bridge. */
842
- writeValue(checked: boolean): void;
843
- render(): lit_html.TemplateResult<1>;
844
- }
845
-
846
1003
  /**
847
1004
  * Single-date form field (Figma "Date Picker"): a text field with typed date entry plus a
848
1005
  * popover calendar whose header pills drill into month and year grids, above
@@ -861,7 +1018,7 @@ declare class RelDatePicker extends LitElement {
861
1018
  placeholder: string;
862
1019
  /** Visible label rendered above the field and wired to the input via `aria-labelledby`; `null` renders no label. */
863
1020
  label: string | null;
864
- /** Disables the field, the icons and the popover; the host dims to 60% opacity. */
1021
+ /** Disables the field, the icons and the popover; the host dims to 40% opacity. */
865
1022
  disabled: boolean;
866
1023
  /** Appends a `*` to the label and sets `aria-required`; no validation is performed. */
867
1024
  required: boolean;
@@ -926,6 +1083,10 @@ declare class RelDatePicker extends LitElement {
926
1083
  private panelView;
927
1084
  /** First year of the visible Years page; re-anchored whenever that view opens. */
928
1085
  private yearPageStart;
1086
+ /** Period-grid cell the keyboard last moved to; -1 until the user moves. Kept
1087
+ * across a year page turn, since it is a grid POSITION and every page has the
1088
+ * same twelve slots. */
1089
+ private periodRoved;
929
1090
  /** Uncommitted typed text; null → the input shows the committed value. */
930
1091
  private inputText;
931
1092
  /** Parse/range error message for the typed text (cleared on next input). */
@@ -1012,12 +1173,22 @@ declare class RelDatePicker extends LitElement {
1012
1173
  private announceYearPage;
1013
1174
  /** A pill toggles its own view; tapping it again returns to the day grid. */
1014
1175
  private switchView;
1015
- /** Focus the selected month/year cell so the keyboard lands somewhere useful. */
1176
+ /** Focus the month/year cell that holds the tab stop, so the keyboard lands
1177
+ * somewhere useful and focus can never disagree with the tab stop. */
1016
1178
  private focusPanelGridSelection;
1017
1179
  private selectMonth;
1018
1180
  private selectYear;
1019
1181
  /** Arrow-key roving across the 3-column month / year grid. */
1020
1182
  private handlePeriodGridKeydown;
1183
+ /**
1184
+ * The tab stop follows focus, wherever focus came from — arrows, a click, Tab,
1185
+ * or a cell that simply kept focus across a re-render. Doing this on focusin
1186
+ * rather than in the key handler is what keeps the two from disagreeing: a
1187
+ * year page turn reuses the same buttons with new labels, so the focused cell
1188
+ * survives while `selected` moves off-page, and deriving the tab stop from
1189
+ * anything other than focus itself puts them on different cells.
1190
+ */
1191
+ private handlePeriodGridFocusin;
1021
1192
  /**
1022
1193
  * Index of the single grid cell carrying tabindex="0". The old expression
1023
1194
  * (`isSelected || (index === 0 && !value)`) left the grid with ZERO tab
@@ -1033,6 +1204,11 @@ declare class RelDatePicker extends LitElement {
1033
1204
  render(): lit_html.TemplateResult<1>;
1034
1205
  }
1035
1206
 
1207
+ /** Field scale, matching the other pickers. */
1208
+ type RangePickerSize = "small" | "medium" | "large";
1209
+ /** Alternative spellings, not deprecations. */
1210
+ type RangePickerSizeAlias = "sm" | "md" | "lg";
1211
+ type RangePickerSizeInput = RangePickerSize | RangePickerSizeAlias;
1036
1212
  /**
1037
1213
  * Date-range form field (Figma "Date Range Picker"): a text field with typed "from → to"
1038
1214
  * entry and a two-pane panel — quick-select presets with single-key shortcuts on the left,
@@ -1044,7 +1220,7 @@ declare class RelDatePicker extends LitElement {
1044
1220
  * @csspart panel - The floating panel (`role="dialog"`) holding presets and calendar
1045
1221
  * @csspart list - The preset column and the day grid
1046
1222
  * @csspart option - Each quick-select preset button
1047
- * @csspart action - The previous-month and next-month buttons
1223
+ * @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
1048
1224
  * @csspart footer - The Reset / Cancel / Apply button row
1049
1225
  */
1050
1226
  declare class RelRangePicker extends LitElement {
@@ -1088,9 +1264,19 @@ declare class RelRangePicker extends LitElement {
1088
1264
  * mirroring rel-time-picker / rel-date-picker. Off by default so the
1089
1265
  * existing intrinsic-width (`inline-block`) rendering is unchanged. */
1090
1266
  fullWidth: boolean;
1267
+ /** Trigger height on the shared field scale — `small` 32 / `medium` 40 / `large` 48px border-box — matching rel-textfield, rel-date-picker and rel-time-picker so the four align in a form row. Also ramps the trigger's text and inline padding. The panel is unaffected: Figma specifies one panel size. `sm` / `md` / `lg` are alternative spellings and stay supported — normalised to the full words, not deprecated. */
1268
+ size: RangePickerSizeInput;
1091
1269
  private draftFrom;
1092
1270
  private draftTo;
1093
1271
  private draftActivePreset;
1272
+ /** Which grid the panel shows. Ported from rel-date-picker so the two
1273
+ * calendars behave the same; reaching January 2023 was ~44 chevron clicks. */
1274
+ private panelView;
1275
+ private yearPageStart;
1276
+ /** Period-grid cell the keyboard last moved to; -1 until the user moves. Kept
1277
+ * across a year page turn, since it is a grid POSITION and every page has the
1278
+ * same twelve slots. */
1279
+ private periodRoved;
1094
1280
  private viewYear;
1095
1281
  private viewMonth;
1096
1282
  private isSelectingRange;
@@ -1115,6 +1301,10 @@ declare class RelRangePicker extends LitElement {
1115
1301
  private handleClickOutside;
1116
1302
  private handleGlobalKeydown;
1117
1303
  private toggleOpen;
1304
+ /** Alternative spellings, NOT deprecations — no console notice. Normalising
1305
+ * keeps the reflected attribute canonical so the :host([size=…]) rules only
1306
+ * need the full words. */
1307
+ protected willUpdate(changed: Map<string, unknown>): void;
1118
1308
  private openPopover;
1119
1309
  private cancelPopover;
1120
1310
  private applySelection;
@@ -1154,6 +1344,48 @@ declare class RelRangePicker extends LitElement {
1154
1344
  private isDateDisabled;
1155
1345
  private getQuickSelects;
1156
1346
  private selectQuickSelect;
1347
+ private goPrev;
1348
+ private goNext;
1349
+ private announceYearPage;
1350
+ /** A pill toggles its own view; tapping it again returns to the day grid. */
1351
+ private switchView;
1352
+ private selectMonth;
1353
+ private selectYear;
1354
+ /**
1355
+ * Arrow-key roving across the Months / Years grid, matching rel-date-picker
1356
+ * exactly. Without this the grid had ONE reachable cell — the selected one —
1357
+ * because every other cell carries tabindex="-1" under the roving pattern and
1358
+ * nothing moved the focus. A keyboard user could open the view and go nowhere,
1359
+ * while the 44 chevron presses still worked: strictly worse than before the
1360
+ * switcher existed.
1361
+ *
1362
+ * Movement clamps at the edges (shared with the date picker through
1363
+ * periodGridTarget); Escape steps back to the day grid rather than falling
1364
+ * through to the panel's own Escape, which closes the whole picker.
1365
+ */
1366
+ private handlePeriodGridKeydown;
1367
+ /**
1368
+ * The tab stop follows focus, wherever focus came from — arrows, a click, Tab,
1369
+ * or a cell that simply kept focus across a re-render. Doing this on focusin
1370
+ * rather than in the key handler is what keeps the two from disagreeing: a
1371
+ * year page turn reuses the same buttons with new labels, so the focused cell
1372
+ * survives while `selected` moves off-page, and deriving the tab stop from
1373
+ * anything other than focus itself puts them on different cells.
1374
+ */
1375
+ private handlePeriodGridFocusin;
1376
+ /**
1377
+ * Return focus to the day grid after leaving Months / Years. Prefers the
1378
+ * roving tab stop and falls back to the first selectable day, so a month with
1379
+ * every leading cell disabled still lands somewhere. Disabled days here carry
1380
+ * a CLASS, not the :disabled state, because they are not buttons.
1381
+ */
1382
+ private focusDayGrid;
1383
+ /** Put focus in the grid when a view opens, so the keyboard has somewhere to
1384
+ * start; otherwise the arrows have nothing to move from. */
1385
+ private focusPeriodGrid;
1386
+ /** Months or Years, in the same 3x4 grid rel-date-picker uses. */
1387
+ private renderPeriodGrid;
1388
+ private getMonthsList;
1157
1389
  private previousMonth;
1158
1390
  private nextMonth;
1159
1391
  private getMonthName;
@@ -1179,65 +1411,11 @@ declare global {
1179
1411
  }
1180
1412
  }
1181
1413
 
1182
- type ChipTone = "neutral" | "danger" | "warning" | "success" | "info";
1183
- type ChipVariant = "fill" | "subtle";
1184
- type ChipSize = "sm" | "md" | "lg";
1185
- type ChipBorder = "default" | "rounded";
1186
- /**
1187
- * Compact chip (Figma "Chip") for tags, filters and selected values: tone x variant
1188
- * colouring, optional leading icon / avatar / trailing icon, a remove control and
1189
- * opt-in drag-and-drop.
1190
- * @csspart base - The chip body (`.chip`) that carries size, tone, variant and border styling
1191
- * @csspart icon - The leading `<rel-icon>` (rendered only when `leading-icon` is set and no avatar)
1192
- * @csspart label - The label text
1193
- * @cssprop --rel-chip-focus-ring - Colour of the keyboard focus outline; default `var(--rel-border-focus)`
1194
- */
1195
- declare class RelChip extends LitElement {
1196
- static shadowRootOptions: ShadowRootInit;
1197
- /** Text shown in the chip; also its accessible name and the drag payload. */
1198
- label: string;
1199
- /** Colour tone (Figma Tone): `neutral`, `danger`, `warning`, `success` or `info`. Combined with `variant`. */
1200
- tone: ChipTone;
1201
- /** Emphasis (Figma Variant): `fill` = solid tone background, `subtle` = tinted background with tone-coloured text. */
1202
- variant: ChipVariant;
1203
- /** Height scale (Figma Size): `sm` 24px / body-sm, `md` 28px / body-md, `lg` 32px / body-lg. Icon size follows (12 / 14 / 16px). */
1204
- size: ChipSize;
1205
- /** Corner shape (Figma Border): `default` = 4px radius, `rounded` = fully rounded with 12px horizontal padding. */
1206
- border: ChipBorder;
1207
- /** Icon name from the generated icon dictionary (e.g. "settings") — rendered via <rel-icon> before the label. Ignored when `avatar` is set. */
1208
- leadingIcon?: string;
1209
- /** Icon name from the generated icon dictionary, rendered after the label. */
1210
- trailingIcon?: string;
1211
- /** Initials text for the avatar (e.g. "JD"), rendered as a `<rel-avatar type="text">` before the label. Mutually exclusive with leadingIcon (avatar wins). */
1212
- avatar?: string;
1213
- /** Shows a remove (close) button after the label; clicking it sets `removed` and fires `removed`. */
1214
- removable: boolean;
1215
- /** `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. */
1216
- removed: boolean;
1217
- /** 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`. */
1218
- draggableChip: boolean;
1219
- /** Reflected so CSS can target the Figma "Dragged" visual state. Driven automatically by dragstart/dragend when draggableChip is set. */
1220
- private dragging;
1221
- static styles: lit.CSSResult;
1222
- private get classes();
1223
- private handleRemove;
1224
- /** The chip body is announced as a button, so it must also act on keys:
1225
- * Enter/Space re-dispatch as a host click (consumers' @click handlers
1226
- * fire), Delete/Backspace remove when removable, and arrow keys ask the
1227
- * container to reorder a draggable chip (`chip-move`) — the keyboard
1228
- * alternative to pointer drag-and-drop. */
1229
- private handleKeydown;
1230
- private onDragStart;
1231
- private onDragEnd;
1232
- protected updated(changed: Map<string, unknown>): void;
1233
- render(): lit_html.TemplateResult<1>;
1234
- }
1235
- declare global {
1236
- interface HTMLElementTagNameMap {
1237
- "rel-chip": RelChip;
1238
- }
1239
- }
1240
-
1414
+ /** Inner padding step, spelled out. */
1415
+ type ContainerPadding = "small" | "medium" | "large" | "extra-large";
1416
+ /** Alternative spellings, not deprecations — the short names keep working. */
1417
+ type ContainerPaddingAlias = "sm" | "md" | "lg" | "xl";
1418
+ type ContainerPaddingInput = ContainerPadding | ContainerPaddingAlias;
1241
1419
  /** Canonical surface names (audit 12). */
1242
1420
  type ContainerSurface = "default" | "elevated" | "transparent";
1243
1421
  /** @deprecated since 1.4.7 — use `ContainerSurface` / the `surface` prop. */
@@ -1249,8 +1427,8 @@ type ContainerBackground = "light" | "white" | "none";
1249
1427
  * @csspart container - Alias of `base`, kept for existing `::part(container)` selectors
1250
1428
  */
1251
1429
  declare class RelContainer extends LitElement {
1252
- /** Inner padding step: `sm` = spacing-xs, `md` = spacing-md, `lg` = spacing-xl, `xl` = spacing-2xl. */
1253
- padding: "sm" | "md" | "lg" | "xl";
1430
+ /** 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. */
1431
+ padding: ContainerPaddingInput;
1254
1432
  /** Adds the shadow-300 elevation box-shadow to the surface. */
1255
1433
  shadow: boolean;
1256
1434
  /**
@@ -1270,7 +1448,8 @@ declare class RelContainer extends LitElement {
1270
1448
 
1271
1449
  /**
1272
1450
  * Thin separator rule that splits content horizontally or vertically. Use it between sections, list groups or toolbar clusters; do not use it as a spacer.
1273
- * @csspart base - The rendered rule (`role="separator"` div) — restyle thickness, colour or margin here.
1451
+ * @csspart base - The rendered rule — restyle thickness, colour or margin here.
1452
+ * @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.
1274
1453
  */
1275
1454
  declare class RelDivider extends LitElement {
1276
1455
  /** 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. */
@@ -1352,12 +1531,23 @@ declare class RelDrawer extends LitElement {
1352
1531
  private previousFocus;
1353
1532
  private startY;
1354
1533
  private currentY;
1534
+ /** Scrim opacity at rest, sampled once per drag so the fade starts from it. */
1535
+ private scrimRestingOpacity;
1355
1536
  private originalBodyOverflow;
1356
1537
  private originalBodyPaddingRight;
1357
1538
  static styles: lit.CSSResult;
1358
1539
  connectedCallback(): void;
1359
1540
  disconnectedCallback(): void;
1360
1541
  updated(changed: Map<string, any>): void;
1542
+ /**
1543
+ * The footer divider is conditional: short content (a confirmation message)
1544
+ * has no divider, but a body that scrolls does — it is a scroll affordance,
1545
+ * not decoration (designer, confirmed). CSS cannot ask whether an element
1546
+ * scrolls, so it is measured and reflected onto the host as `body-scrolls`,
1547
+ * re-measured whenever the body or its contents resize.
1548
+ */
1549
+ private bodyScrollObserver?;
1550
+ private watchBodyScroll;
1361
1551
  private handleOpen;
1362
1552
  private handleClose;
1363
1553
  private lockScroll;
@@ -1393,7 +1583,7 @@ type KbdSize = "sm" | "md" | "lg" | "small" | "medium" | "large";
1393
1583
  declare class RelKbd extends LitElement {
1394
1584
  /** 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. */
1395
1585
  size: KbdSize;
1396
- /** Fill / text contrast: `default` = elevated-1 background with label text; `muted` = elevated-2 background with placeholder-tone text for secondary shortcuts. Both are flat with a 1px border. */
1586
+ /** 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. */
1397
1587
  variant: "default" | "muted";
1398
1588
  /**
1399
1589
  * Fully rounded corners. Logs a one-time console notice when set.
@@ -1447,6 +1637,8 @@ type StepperType = "default" | "dot" | "number" | "icon";
1447
1637
  type LabelPlacement = "top" | "centered" | "inverted";
1448
1638
  interface Step {
1449
1639
  title: string;
1640
+ /** Optional second line under the title (Figma "Subtitle" / "Show Subtitle"). Omit it and the step stays single-line. */
1641
+ subtitle?: string;
1450
1642
  status: StepStatus;
1451
1643
  }
1452
1644
  /**
@@ -1454,13 +1646,15 @@ interface Step {
1454
1646
  * @csspart indicator - The 24px circle (8px for `type="dot"`) that holds the step number, check mark or icon.
1455
1647
  * @csspart track - The 1px connector line from a step to the next one (not rendered on the last step).
1456
1648
  * @csspart label - The step title text.
1649
+ * @csspart subtitle - The optional second line under the title; rendered only when a step sets `subtitle`.
1457
1650
  * @cssprop --rel-stepper-indicator-size - Indicator diameter (default 24px; `type="dot"` is fixed at 8px).
1458
- * @cssprop --rel-stepper-connector-length-lg - Connector length and step gap for `size="large"` in both orientations (default 86px).
1459
- * @cssprop --rel-stepper-connector-length-md - Connector length and step gap for `size="small"` in `orientation="horizontal"` (default 60px).
1460
- * @cssprop --rel-stepper-connector-length-sm - Connector length and step gap for `size="small"` in `orientation="vertical"` (default 42px).
1651
+ * @cssprop --rel-stepper-connector-length - Length of the connector line; the step gap is derived from it by adding Figma's clear space at both ends. Defaults to Figma's fixed geometry: 96px horizontally, 34px vertically with `label-placement="centered"`, 51px with `inverted`.
1652
+ * @cssprop --rel-stepper-connector-length-lg - Superseded by `--rel-stepper-connector-length`, which it still falls back to, so an existing override keeps working.
1653
+ * @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.
1654
+ * @cssprop --rel-stepper-connector-length-sm - No longer applies, for the same reason as `-md`.
1461
1655
  */
1462
1656
  declare class RelStepper extends LitElement {
1463
- /** The steps in order, each `{ title: 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. */
1657
+ /** 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. */
1464
1658
  steps: Step[];
1465
1659
  /**
1466
1660
  * Legacy label placement (attribute `textalign`): `below` (default) puts the label under the indicator, `right` puts it beside the indicator. Ignored whenever `label-placement` is set.
@@ -1469,13 +1663,22 @@ declare class RelStepper extends LitElement {
1469
1663
  textAlign: TextAlign;
1470
1664
  /** Layout axis: `horizontal` (default) lays the steps out in a centred row; `vertical` stacks them. */
1471
1665
  orientation: Orientation;
1472
- /** `large` (default) or `small`: controls the label type (body-md vs body-sm) and the connector length / step gap (see the `--rel-stepper-connector-length-*` hooks). */
1666
+ /**
1667
+ * No longer has any effect. It used to set the connector length and step gap
1668
+ * (86px for `large`, 60 / 42px for `small`) and to swap the step label to
1669
+ * body/sm/regular at `small`.
1670
+ * @deprecated Figma has no Size axis on the Stepper — the connector geometry
1671
+ * is fixed — so this was a code-only scale. It stays accepted so existing
1672
+ * markup keeps working, and now resolves to the one Figma geometry. Retune it
1673
+ * with `--rel-stepper-connector-length` instead. Logs a one-time notice.
1674
+ */
1473
1675
  size: StepperSize;
1474
1676
  /** Figma "Type" axis — what the indicator shows: `default` (step number, check mark once done), `dot` (8px dot, no glyph), `number` (always the number) or `icon` (always the check mark). */
1475
1677
  type: StepperType;
1476
1678
  /** 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. */
1477
1679
  labelPlacement?: LabelPlacement;
1478
1680
  static styles: lit.CSSResult;
1681
+ protected willUpdate(changed: Map<string, unknown>): void;
1479
1682
  private isDone;
1480
1683
  private isProgress;
1481
1684
  private isDisabled;
@@ -1497,7 +1700,11 @@ interface RadioOption {
1497
1700
  * Ignored when `icon` is also set. */
1498
1701
  avatar?: string;
1499
1702
  }
1703
+ /** Control scale, spelled out. */
1500
1704
  type RadioSize = "small" | "medium" | "large";
1705
+ /** Alternative spellings, not deprecations — the short names work too. */
1706
+ type RadioSizeAlias = "sm" | "md" | "lg";
1707
+ type RadioSizeInput = RadioSize | RadioSizeAlias;
1501
1708
  type RadioDensity = "compact" | "comfortable";
1502
1709
  type RadioAlignment = "leading" | "trailing";
1503
1710
  /**
@@ -1518,11 +1725,18 @@ declare class RelRadio extends LitElement {
1518
1725
  /** Layout axis of the option list. `"vertical"` (default) stacks options in
1519
1726
  * a column; `"horizontal"` lays them out in a wrapping row. */
1520
1727
  orientation: Orientation;
1521
- /** Control scale (Figma Size). `"medium"` (default, Figma M) = 20px control
1522
- * with body-lg labels; `"large"` (Figma L) = 24px control with body-xl
1523
- * labels; `"small"` (Figma S) = 20px control with body-md labels — S and M
1524
- * share the control size in the spec, only typography steps down. */
1525
- size: RadioSize;
1728
+ /** Control scale. `"medium"` (default, Figma M) = 20px control with body-lg
1729
+ * labels; `"large"` (Figma L) = 24px control with body-xl labels;
1730
+ * `"small"` = 16px control with body-md labels. `sm` / `md` / `lg` are
1731
+ * alternative spellings and stay supported — normalised to the full words,
1732
+ * not deprecated.
1733
+ *
1734
+ * DELIBERATE FIGMA DIVERGENCE on Small. Figma's `.Radio Control` has one
1735
+ * size (20x20) and its row variants pin Small AND Medium to 20, so the two
1736
+ * rendered identically and the Size axis did nothing between them. 16px is
1737
+ * not invented: it is rel-checkbox's Small, and checkbox is radio's sibling
1738
+ * selection control. See the size ramp in the styles below. */
1739
+ size: RadioSizeInput;
1526
1740
  /** Supporting-text layout (Figma Density). `"compact"` (default) puts the
1527
1741
  * supporting text on its own line under the row, indented past the control;
1528
1742
  * `"comfortable"` stacks label + supporting text beside the control. */
@@ -1574,7 +1788,15 @@ declare class RelRadio extends LitElement {
1574
1788
  render(): lit_html.TemplateResult<1>;
1575
1789
  }
1576
1790
 
1577
- type MenuType = "default" | "checkbox" | "arrow" | "select-option" | "fixed" | "fit";
1791
+ /**
1792
+ * Row shape (Figma ".Menu Item" Type): list | checkbox | select-option.
1793
+ * `default` is the deprecated spelling of `list`. `arrow` is a code-only
1794
+ * extension with no Figma counterpart — it turns on the submenu flyout.
1795
+ * `fixed` / `fit` are deprecated here and now live on `width`.
1796
+ */
1797
+ type MenuType = "list" | "checkbox" | "select-option" | "arrow" | "default" | "fixed" | "fit";
1798
+ /** Sizing mode (Figma "Menu" Type). Both modes are --rel-menu-width wide; the axis varies the HEIGHT: fixed pins the panel to --rel-menu-height and scrolls, fit hugs its rows. */
1799
+ type MenuWidth = "fixed" | "fit";
1578
1800
  /** Figma ".Menu Item" Size: large (48px rows) | medium (40px) | small (32px). */
1579
1801
  type MenuSize = "large" | "medium" | "small";
1580
1802
  interface MenuItem {
@@ -1607,15 +1829,36 @@ declare class RelMenuItem extends LitElement {
1607
1829
  * rows, checkbox rows, radio-style select-option rows, or hover/keyboard submenus.
1608
1830
  * @csspart list - The `role="menu"` root list (a light-DOM `[part="list"]` attribute)
1609
1831
  * @csspart panel - A submenu flyout (`type="arrow"` only; light-DOM `[part="panel"]` attribute)
1610
- * @cssprop --rel-menu-width - Width of the menu when `type="fixed"`; default 300px
1832
+ * @cssprop --rel-menu-width - Width of the menu in BOTH sizing modes; default 300px
1833
+ * @cssprop --rel-menu-height - Height of the menu when `width="fixed"`, which then scrolls; default 500px. `fit` ignores it and hugs its rows.
1611
1834
  */
1612
1835
  declare class RelMenu extends LitElement {
1613
- /** Menu behaviour / width mode (Figma Type). `default` = single-select rows
1614
- * (`rel-select`); `checkbox` = toggling rows with a checkbox (`rel-check`);
1615
- * `select-option` = single-select rows with a radio control; `arrow` = rows whose
1616
- * `submenu` opens a flyout (`rel-submenu-select`); `fixed` / `fit` behave like
1617
- * `default` but size the menu to `--rel-menu-width` (300px) or to its content. */
1836
+ /**
1837
+ * Row shape (Figma ".Menu Item" Type). `list` (default) = plain rows;
1838
+ * `checkbox` = toggling rows with a checkbox; `select-option` = single-select
1839
+ * rows with a trailing check.
1840
+ * `arrow` is a code-only extension with no Figma counterpart: rows whose
1841
+ * `submenu` opens a flyout. Because it rides this axis, submenus cannot
1842
+ * currently be combined with checkbox or select-option rows — see the README.
1843
+ * DEPRECATED values, all still working: `default` is the old spelling of
1844
+ * `list`; `fixed` / `fit` were width modes and now set `width` instead,
1845
+ * leaving the row shape at `list`.
1846
+ */
1618
1847
  type: MenuType;
1848
+ /**
1849
+ * Sizing mode (Figma "Menu" Type). Both modes are `--rel-menu-width` (300px)
1850
+ * wide — Figma's Fixed and Fit variants are both 300px — and the axis varies
1851
+ * the HEIGHT: `fixed` pins the panel to `--rel-menu-height` (500px) and
1852
+ * scrolls, `fit` hugs its rows. `fit` previously varied the width instead,
1853
+ * which was the wrong dimension. This was also fused into `type`, where
1854
+ * choosing a width silently forced plain rows and choosing a row shape
1855
+ * silently forced the default width.
1856
+ */
1857
+ width?: MenuWidth;
1858
+ /** Row shape actually rendered: the deprecated width values fall back to `list`. */
1859
+ private get resolvedType();
1860
+ /** Width mode actually applied: explicit `width` wins, else the deprecated `type` spelling. */
1861
+ private get resolvedWidth();
1619
1862
  /**
1620
1863
  * Row size (Figma ".Menu Item" Size, 6326:616): large = 48px rows with
1621
1864
  * body-lg labels (Figma default), medium = 40px / body-md, small = 32px /
@@ -1632,7 +1875,7 @@ declare class RelMenu extends LitElement {
1632
1875
  private _items;
1633
1876
  /** Stable id prefix for aria-activedescendant targets. */
1634
1877
  private menuId;
1635
- 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 min-width: 220px;\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: 2px;\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 }\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 font-weight: var(--rel-body-lg-medium-font-weight);\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 .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) */\n .rel-menu.fixed {\n width: var(--rel-menu-width, 300px);\n min-width: 0;\n box-sizing: border-box;\n }\n\n .rel-menu.fit {\n min-width: 0;\n width: max-content;\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 .submenu-chevron {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n opacity: 0.6;\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 .rel-submenu {\n border: 1px solid var(--rel-border-subtle);\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: 200px;\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 ";
1878
+ 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 .submenu-chevron {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n opacity: 0.6;\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 ";
1636
1879
  createRenderRoot(): this;
1637
1880
  connectedCallback(): void;
1638
1881
  disconnectedCallback(): void;
@@ -1666,7 +1909,7 @@ declare class RelMenubar extends LitElement {
1666
1909
  */
1667
1910
  bindShortcuts: boolean;
1668
1911
  /**
1669
- * Width mode (Figma "Menu Bar" Type, 8699:4): `fit` sizes the bar to its triggers, `fixed` stretches it across its container (override the stretched width with `--rel-menubar-width`). Added in 1.4.8; the default stays `fit`, which is how the bar has always rendered.
1912
+ * Width mode (Figma "Menu Bar" Type, 8699:4): `fit` sizes the bar to its triggers, `fixed` stretches it across its container (override the stretched width with `--rel-menubar-width`). The default stays `fit`, which is how the bar has always rendered.
1670
1913
  */
1671
1914
  type: MenubarType;
1672
1915
  private menus;
@@ -1758,7 +2001,7 @@ declare class RelOtp extends LitElement {
1758
2001
  value: string;
1759
2002
  /** Total number of cells (characters in the code). */
1760
2003
  length: number;
1761
- /** Cells per visual group; a separator renders between groups. `0` (or `show-separator` off) renders one ungrouped row. */
2004
+ /** Cells per visual group; a separator renders between groups. Grouping applies only when `length` divides evenly by this — otherwise the row renders ungrouped, because a ragged final group would put a separator before a lone cell. So the default `length="6"` is 3–3, `length="4"` is one ungrouped row of four, and `group-size="2"` with `length="4"` is 2–2. `0` (or `show-separator` off) also renders one ungrouped row. */
1762
2005
  groupSize: number;
1763
2006
  /** Placeholder character shown in every empty cell. */
1764
2007
  placeholder: string;
@@ -1775,7 +2018,7 @@ declare class RelOtp extends LitElement {
1775
2018
  type: OtpType;
1776
2019
  /** Renders the cells as `type="password"` so entered characters are masked. */
1777
2020
  mask: boolean;
1778
- /** Disables every cell and dims the control to 0.6 opacity. */
2021
+ /** Disables every cell and dims the control to 0.4 opacity (cell fill and border unchanged, as Figma specifies). */
1779
2022
  disabled: boolean;
1780
2023
  /** Makes every cell read-only: the value is shown but typing, paste and keyboard editing are ignored. */
1781
2024
  readonly: boolean;
@@ -1793,7 +2036,7 @@ declare class RelOtp extends LitElement {
1793
2036
  ariaLabel: string | null;
1794
2037
  /** Error styling (danger border on every cell) and `aria-invalid="true"`. */
1795
2038
  invalid: boolean;
1796
- /** Renders the dash separator between groups. Boolean attribute: its presence means true — set the property to `false` to hide separators. */
2039
+ /** 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`. */
1797
2040
  showSeparator: boolean;
1798
2041
  /** 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. */
1799
2042
  size: OtpSize;
@@ -1853,8 +2096,17 @@ declare class RelPagination extends LitElement {
1853
2096
  /** 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. */
1854
2097
  perPage: number;
1855
2098
  /** Shows a "Viewing page X of Y" caption under the extended layout (ignored by the other types). */
2099
+ /**
2100
+ * DELIBERATE DIVERGENCE — do not flip this default to `true`. Figma's
2101
+ * `Type=Extended` has no boolean for the caption: the "Viewing page 1 of 10"
2102
+ * text is a fixed child of both Extended variants and is load-bearing for
2103
+ * their 78px height (40 + spacing-md + 22). Here it stays opt-in. Ruled by
2104
+ * the user: our behaviour stands and the DESIGN FILE follows our docs.
2105
+ * Defaulting it on would silently add 38px to every existing extended
2106
+ * pagination.
2107
+ */
1856
2108
  showLabel: boolean;
1857
- /** Dims the whole control to 50% opacity, shows a `not-allowed` cursor and ignores every page change. */
2109
+ /** Dims the whole control to 40% opacity, shows a `not-allowed` cursor and ignores every page change. */
1858
2110
  disabled: boolean;
1859
2111
  /** Reserved for link-mode pagination. Currently accepted but not rendered: every page control is a `<button>`, so navigate in your `rel-change` handler instead. */
1860
2112
  hrefTemplate: string;
@@ -1888,7 +2140,7 @@ type AlignType = "start" | "center" | "end";
1888
2140
  * @slot content - The panel body, usually a `<rel-popover-content>`; rendered inside the fixed, top-layer wrapper only while open.
1889
2141
  * @csspart arrow - The 14×14 chevron box rendered on the panel edge facing the trigger when `arrow` is set (absent otherwise).
1890
2142
  * @cssprop --rel-popover-arrow-bg - Fill of the `arrow` chevron (default `--rel-bg-neutral`, matching `<rel-popover-content>`).
1891
- * @cssprop --rel-popover-arrow-border - Stroke of the `arrow` chevron's two slanted edges (default `--rel-border-default`).
2143
+ * @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.
1892
2144
  */
1893
2145
  declare class RelPopover extends LitElement {
1894
2146
  /** 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. */
@@ -1974,6 +2226,11 @@ declare global {
1974
2226
  }
1975
2227
  }
1976
2228
 
2229
+ /** Figma Size axis, spelled out. */
2230
+ type PopoverContentSize = "small" | "medium" | "large";
2231
+ /** Alternative spellings, not deprecations — the short names keep working. */
2232
+ type PopoverContentSizeAlias = "sm" | "md" | "lg";
2233
+ type PopoverContentSizeInput = PopoverContentSize | PopoverContentSizeAlias;
1977
2234
  /**
1978
2235
  * 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.
1979
2236
  * @slot - The panel content (headings, paragraphs, form controls, buttons).
@@ -1982,8 +2239,12 @@ declare global {
1982
2239
  declare class RelPopoverContent extends LitElement {
1983
2240
  /** Accessible name applied to the inner `role="dialog"` box (attribute `aria-label`); describe what the panel is for. */
1984
2241
  ariaLabel: string;
1985
- /** Figma Size axis (S/M/L): panel padding 12/16/20px. */
1986
- size: "sm" | "md" | "lg";
2242
+ /** 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. */
2243
+ size: PopoverContentSizeInput;
2244
+ /** Alternative spellings, NOT deprecations — no console notice. Normalising
2245
+ * keeps the reflected attribute canonical, so the :host([size=…]) rules only
2246
+ * need the full words. */
2247
+ protected willUpdate(changed: Map<string, unknown>): void;
1987
2248
  static styles: lit.CSSResult;
1988
2249
  render(): lit_html.TemplateResult<1>;
1989
2250
  }
@@ -1995,7 +2256,7 @@ type ProgressMode = "linear-determinate" | "linear-indeterminate" | "circular-de
1995
2256
  * @csspart indicator - The filled / sliding bar inside the linear track (linear modes only)
1996
2257
  */
1997
2258
  declare class RelProgress extends LitElement {
1998
- /** Shape and behaviour: `linear-*` is a full-width 6px bar, `circular-*` a 48px ring; `*-determinate` fills to `value`, `*-indeterminate` loops an animation and ignores `value`. */
2259
+ /** 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`. */
1999
2260
  mode: ProgressMode;
2000
2261
  /** Completion percentage 0–100 for the determinate modes; out-of-range values are clamped before rendering and before `aria-valuenow`. */
2001
2262
  value: number;
@@ -2107,16 +2368,24 @@ interface SegmentOption {
2107
2368
  disabled?: boolean;
2108
2369
  /** `rel-icon` name rendered before the label (Figma "Left Icon"), sized 16 / 18 / 20 with the control. */
2109
2370
  icon?: string;
2371
+ /**
2372
+ * Trailing badge (Figma "Show Badge" / "Badge"), rendered after the label
2373
+ * inside the segment's own 8px gap. A number or string shows the count at
2374
+ * `medium` and `large`; at `small` Figma switches to the 6px dot, so the
2375
+ * value is not displayed there. Use `true` for a dot at any size.
2376
+ */
2377
+ badge?: number | string | boolean;
2110
2378
  }
2111
2379
  /**
2112
2380
  * 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.
2113
2381
  * @csspart base - The `role="radiogroup"` track that lays out the segments.
2114
2382
  * @csspart option - Each segment's native `<button role="radio">`; the active one carries the `active` class and `aria-checked="true"`.
2115
2383
  * @csspart icon - A segment's optional leading `rel-icon` element.
2384
+ * @csspart badge - A segment's optional trailing `rel-badge`; rendered only when the option sets `badge`.
2116
2385
  * @csspart label - The wrapper around a segment's label text.
2117
2386
  */
2118
2387
  declare class RelSegmentedButton extends LitElement {
2119
- /** The segments in order, each `{ label: string; value: string; disabled?: boolean; icon?: string }`. 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. */
2388
+ /** 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. */
2120
2389
  options: SegmentOption[];
2121
2390
  /** `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). */
2122
2391
  active: string;
@@ -2132,6 +2401,13 @@ declare class RelSegmentedButton extends LitElement {
2132
2401
  * one Tab stop (roving tabindex on the selected segment). */
2133
2402
  private onKeyDown;
2134
2403
  private renderSegment;
2404
+ /**
2405
+ * Figma's Show Badge axis: a 20x20 numeric badge at medium and large, and a
2406
+ * 6px dot at small — where the count is not shown at all, so a numeric value
2407
+ * degrades to a dot rather than being squeezed in. `badge: true` asks for the
2408
+ * dot at any size.
2409
+ */
2410
+ private renderBadge;
2135
2411
  render(): lit_html.TemplateResult<1>;
2136
2412
  }
2137
2413
 
@@ -2159,7 +2435,9 @@ type SelectOption = string | SelectOptionObject;
2159
2435
  * @csspart list - Alias of `panel`
2160
2436
  * @csspart input - The search input inside the panel
2161
2437
  * @csspart option - A single-select option row
2162
- * @cssprop --rel-field-label-reserve - Height held for the absent label row under `labelStyle="none"` so the trigger bottom-aligns with labelled siblings; defaults to the label line-height plus the wrapper gap. Set to `0` to collapse the row entirely (toolbars, table cells).
2438
+ * @csspart option-label - A single-select row's first line (the option label)
2439
+ * @csspart option-supporting-text - A single-select row's optional second line; rendered only when the option supplies `supportingText`
2440
+ * @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).
2163
2441
  * @cssprop --rel-select-bg - Background of the trigger, the floating label, the panel and its sticky header
2164
2442
  * @cssprop --rel-select-bg-disabled - Trigger background when `state="disabled"`
2165
2443
  * @cssprop --rel-select-border-default - Trigger border colour at rest
@@ -2200,7 +2478,14 @@ declare class RelSelect extends LitElement {
2200
2478
  /**
2201
2479
  * Options to list. Each entry is a plain string (used as both value and label) or
2202
2480
  * `{ value, label?, supportingText?, icon? }`; `icon` is a `rel-icon` name rendered
2203
- * before the label, `supportingText` shows only in multi-select rows.
2481
+ * before the label, `supportingText` renders as a second line in both single- and multi-select rows.
2482
+ *
2483
+ * NOTE — the panel's search matches the LABEL ONLY. `supportingText` is displayed but not
2484
+ * searched, so an option is not findable by its second line here. This differs deliberately from
2485
+ * `rel-autocomplete`, whose filter matches the supporting line as well: there the second line
2486
+ * carries identifying data (a DOT or MC number) that a user types to find the row, whereas a
2487
+ * select's is descriptive ("Read-only", "Can edit, not delete") and matching it would return rows
2488
+ * whose visible label has nothing to do with the query.
2204
2489
  */
2205
2490
  options: SelectOption[];
2206
2491
  /** Enables multi-select: selected values render as removable chips, options as checkboxes, and a Select-all row appears in the panel. */
@@ -2245,6 +2530,16 @@ declare class RelSelect extends LitElement {
2245
2530
  */
2246
2531
  maxVisibleChips: number;
2247
2532
  /** Hides the in-panel search field. Figma's "Single Option" dropdown has no search bar at all — use this for short, non-searchable lists. */
2533
+ /**
2534
+ * DELIBERATE DIVERGENCE — do not default this to `!multiple`. Figma's
2535
+ * `.Menu Dropdown` shows the search row only on `Type=Multioption`; its
2536
+ * `Type=Single Option` panels contain five Select Option items and nothing
2537
+ * else. Here the search row renders for single-select too unless this is set.
2538
+ * Ruled by the user: our behaviour stands and the DESIGN FILE follows our
2539
+ * docs. A long single-select list is unusable without search, and making the
2540
+ * default depend on `multiple` would change the panel of every existing
2541
+ * single-select.
2542
+ */
2248
2543
  noSearch: boolean;
2249
2544
  private open;
2250
2545
  private highlightedIndex;
@@ -2290,11 +2585,15 @@ declare class RelSelect extends LitElement {
2290
2585
  */
2291
2586
  private get chipSize();
2292
2587
  /**
2293
- * Multi-select rows nest the checkbox one step down its own ramp: rel-checkbox
2294
- * sits a size above the rest of the system (its `large` is body/xl where a
2295
- * `large` field is body/lg), so large select -> medium checkbox and both
2296
- * medium and small select -> small checkbox, the smallest it ships. The rows
2297
- * still land on 48 / 40 / 32 through their own padding and min-height.
2588
+ * Multi-select rows nest the checkbox at the SAME step as the field. This
2589
+ * used to shift one down, compensating for rel-checkbox sitting a size above
2590
+ * the rest of the system — but that ramp has since been realigned to Figma
2591
+ * (16/18/20/24 with body-sm/md/lg/xl labels), so the compensation is now the
2592
+ * bug: it is what made a small multi-select render 14px labels where Figma
2593
+ * asks for 12px. Figma's option content heights are 20 / 22 / 24 for
2594
+ * small / medium / large, which is exactly body-sm / body-md / body-lg — the
2595
+ * checkbox step of the same name. The rows still land on 32 / 40 / 48
2596
+ * through their own padding and min-height.
2298
2597
  */
2299
2598
  private get optionCheckboxSize();
2300
2599
  private get selectAllStatus();
@@ -2345,18 +2644,19 @@ type SkeletonAnimation = "pulse" | "shimmer" | "none";
2345
2644
  * Loading placeholder: a neutral block, circle or stack of text lines that pulses or shimmers while real content loads, then hides itself once `loaded` is set. Use it in place of content whose size you already know; use a spinner for indeterminate waits.
2346
2645
  * @csspart base - Each placeholder surface: the single block/circle, or every bar when `shape="line"`.
2347
2646
  * @cssprop --rel-skeleton-color - Fill colour of the placeholder (defaults to the elevated-3 neutral background token).
2647
+ * @cssprop --rel-skeleton-line-height - Height of each text bar when `shape="line"`; default 16px (Figma's Line shape).
2348
2648
  * @cssprop --rel-skeleton-highlight - Lighter band that sweeps across the placeholder when `animation="shimmer"` (defaults to the elevated-2 neutral background token).
2349
2649
  */
2350
2650
  declare class RelSkeleton extends LitElement {
2351
- /** Placeholder shape: `block` fills the host (min 16px tall), `circle` is a square-aspect disc sized by the host width, `line` renders `lines` 14px text bars with the last one at 65% width. */
2651
+ /** 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. */
2352
2652
  shape: SkeletonShape;
2353
- /** Number of text bars when `shape="line"` (minimum 1, 8px apart); ignored by the other shapes. */
2653
+ /** Number of text bars when `shape="line"` (minimum 1, --rel-spacing-xs apart); ignored by the other shapes. */
2354
2654
  lines: number;
2355
2655
  /** Any CSS width (e.g. `"240px"`, `"50%"`) applied to the host's inline style. Empty leaves the host at its natural block width. */
2356
2656
  width: string;
2357
2657
  /** Any CSS height (e.g. `"120px"`) applied to the host's inline style. Not needed for `circle`, which derives its height from the width. */
2358
2658
  height: string;
2359
- /** Motion: `pulse` fades opacity between 100% and 40%, `shimmer` sweeps a highlight band left to right, `none` is static. Both animations are disabled under `prefers-reduced-motion: reduce`. */
2659
+ /** Motion: `pulse` fades opacity between 20% and 40% (Figma's Start/End states), `shimmer` sweeps a highlight band left to right, `none` is static. Both animations are disabled under `prefers-reduced-motion: reduce`. */
2360
2660
  animation: SkeletonAnimation;
2361
2661
  /** Set once the real content is ready: hides the skeleton (`display: none`) and removes its `role="status"` / "Loading" label. */
2362
2662
  loaded: boolean;
@@ -2371,7 +2671,11 @@ declare global {
2371
2671
  }
2372
2672
 
2373
2673
  type ModalSide = "top" | "bottom" | "center";
2374
- type ModalSize = "sm" | "md" | "lg" | "xl" | "fullscreen";
2674
+ /** Panel width scale, spelled out. `fullscreen` has no short form. */
2675
+ type ModalSize = "small" | "medium" | "large" | "extra-large" | "fullscreen";
2676
+ /** Alternative spellings, not deprecations — the short names keep working. */
2677
+ type ModalSizeAlias = "sm" | "md" | "lg" | "xl";
2678
+ type ModalSizeInput = ModalSize | ModalSizeAlias;
2375
2679
  /**
2376
2680
  * `"default"` — the body is a free-form slot (24px padding, your own layout).
2377
2681
  * `"text"` — the Figma Modal Sheet "Text" slot: a message-only modal whose body
@@ -2413,8 +2717,8 @@ declare class RelModal extends LitElement {
2413
2717
  dismissible: boolean;
2414
2718
  /** Modal mode: renders the scrim, sets `role="dialog"` + `aria-modal`, traps Tab inside the panel and moves focus in on open. When false the panel is a `region` with no scrim and no focus trap. */
2415
2719
  modal: boolean;
2416
- /** Panel width scale. `md` matches the Figma Modal Sheet width; `fullscreen` covers the viewport. */
2417
- size: ModalSize;
2720
+ /** Panel width scale. `medium` (default) matches the Figma Modal Sheet width; `fullscreen` covers the viewport. `sm` / `md` / `lg` / `xl` are alternative spellings of `small` / `medium` / `large` / `extra-large` and stay supported — they are normalised to the full words, not deprecated. */
2721
+ size: ModalSizeInput;
2418
2722
  /** Body treatment — see `ModalVariant`. `"text"` styles the default slot as
2419
2723
  * the Figma "Text" message slot; the header (and its divider) render as
2420
2724
  * usual above it. Default rendering is unchanged. */
@@ -2449,8 +2753,18 @@ declare class RelModal extends LitElement {
2449
2753
  private originalBodyPaddingRight;
2450
2754
  static styles: lit.CSSResult;
2451
2755
  connectedCallback(): void;
2756
+ /**
2757
+ * The footer divider is conditional: the designer confirmed that short
2758
+ * content (a confirmation message) has no divider, but a body that scrolls
2759
+ * does — it is a scroll affordance, not decoration. There is no CSS way to
2760
+ * ask "does this element scroll", so it is measured and reflected onto the
2761
+ * host as `body-scrolls`, re-measured whenever the body or its contents
2762
+ * resize.
2763
+ */
2764
+ private bodyScrollObserver?;
2765
+ private watchBodyScroll;
2452
2766
  disconnectedCallback(): void;
2453
- protected willUpdate(): void;
2767
+ protected willUpdate(changed: Map<string, unknown>): void;
2454
2768
  updated(changed: Map<string, unknown>): void;
2455
2769
  private handleOpen;
2456
2770
  private handleClose;
@@ -2473,6 +2787,11 @@ declare global {
2473
2787
  */
2474
2788
  type SliderType = "continuous" | "discrete" | "default" | "range";
2475
2789
  type TrackStyle = "solid" | "dotted";
2790
+ /** Figma Size axis, spelled out as in the design file. */
2791
+ type SliderSize = "small" | "medium" | "large";
2792
+ /** Alternative spellings, not deprecations — the original short names keep working. */
2793
+ type SliderSizeAlias = "sm" | "md" | "lg";
2794
+ type SliderSizeInput = SliderSize | SliderSizeAlias;
2476
2795
  /**
2477
2796
  * Slider (Figma "Slider", 6417:1340): a pill track with a stroked round thumb,
2478
2797
  * optional value tooltip, discrete tick marks and a dual-thumb range mode.
@@ -2491,8 +2810,19 @@ type TrackStyle = "solid" | "dotted";
2491
2810
  declare class RelSlider extends LitElement {
2492
2811
  /** 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`. */
2493
2812
  type: SliderType;
2494
- /** Figma Size axis (S/M/L): scales track, thumb and ticks. */
2495
- size: "sm" | "md" | "lg";
2813
+ /**
2814
+ * Draws the tick marks (Figma's separate `Is Determinated` boolean). Works
2815
+ * with ANY `type` — Figma shows ticks on every variant, where the code used
2816
+ * to gate them behind the code-only `type="discrete"`, so a `default` or
2817
+ * `range` slider could never show them.
2818
+ * Left unset it follows `type="discrete"`, so nothing changes for existing
2819
+ * sliders; set it explicitly to override in either direction.
2820
+ */
2821
+ showTicks?: boolean;
2822
+ /** Whether tick marks render: explicit `show-ticks` wins, else the legacy `type="discrete"`. */
2823
+ private get ticksVisible();
2824
+ /** Figma Size axis: `small` (6px track / 16px thumb), `medium` (8 / 20, default) or `large` (10 / 24), which also scales the ticks and the value bubble. `sm` / `md` / `lg` are alternative spellings and stay supported — they are normalised to the full words, not deprecated. */
2825
+ size: SliderSizeInput;
2496
2826
  /** Lowest selectable value. */
2497
2827
  min: number;
2498
2828
  /** Highest selectable value. */
@@ -2518,6 +2848,10 @@ declare class RelSlider extends LitElement {
2518
2848
  private _onTouched;
2519
2849
  static styles: lit.CSSResult;
2520
2850
  connectedCallback(): void;
2851
+ /** Alternative spellings, NOT deprecations — no console notice. Normalising
2852
+ * the property rather than duplicating every [size=] selector keeps the
2853
+ * reflected attribute canonical, so only one spelling can reach the CSS. */
2854
+ protected willUpdate(changed: Map<string, unknown>): void;
2521
2855
  updated(changedProperties: Map<string, any>): void;
2522
2856
  private get isRange();
2523
2857
  private get percentage();
@@ -2549,6 +2883,8 @@ declare class RelSlider extends LitElement {
2549
2883
  }
2550
2884
 
2551
2885
  type SnackbarLines = "single" | "double";
2886
+ /** Canonical layout axis (Figma "Type"): default | longer-action. */
2887
+ type SnackbarVariant = "default" | "longer-action";
2552
2888
  /** Canonical semantic tones (Figma "Tone"): neutral | success | warning | destructive. */
2553
2889
  type SnackbarTone = "neutral" | "success" | "warning" | "destructive";
2554
2890
  /** Deprecated spellings: "default" → neutral; "error" | "danger" | "critical" → destructive. */
@@ -2567,13 +2903,17 @@ declare class RelSnackbar extends LitElement {
2567
2903
  message: string;
2568
2904
  /** Label of the optional action button (Figma trailing button, Static / Small); omitted when unset. Clicking it fires `actionClick` and then dismisses the snackbar. */
2569
2905
  action?: string;
2570
- /** Doubles the auto-dismiss delay (`duration * 2`) so the user has time to read and use the action button. Pair it with `lines="double"` for the Figma Type=Longer Action layout. */
2906
+ /**
2907
+ * 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.
2908
+ */
2909
+ variant?: SnackbarVariant;
2910
+ /** @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. */
2571
2911
  longerAction: boolean;
2572
2912
  /** 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. */
2573
2913
  iconClose: boolean;
2574
- /** Layout: `"single"` (default) puts the message and the actions on one row; `"double"` stacks the message above the actions, which align to the end (Figma Type=Longer Action). */
2914
+ /** @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. */
2575
2915
  lines: SnackbarLines;
2576
- /** Auto-dismiss delay in milliseconds (doubled by `longerAction`), started when `show` becomes true and restarted when `duration` or `longerAction` changes while shown. `0` (or a negative value) disables auto-dismiss. */
2916
+ /** 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. */
2577
2917
  duration: number;
2578
2918
  /**
2579
2919
  * Semantic tone (Figma "Tone"): `"neutral"` (default: no accent, no icon) | `"success"` | `"warning"` | `"destructive"` — the status tones add a 4px left accent and a leading icon (check-circle / warning / error) in the tone colour. `"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.
@@ -2583,6 +2923,8 @@ declare class RelSnackbar extends LitElement {
2583
2923
  type?: SnackbarToneInput;
2584
2924
  /** Whether the snackbar is visible. Set it to true to show the snackbar and start the auto-dismiss timer; it is set back to false by the close icon, the action button and the timer. There is no open/close method — toggle this property. */
2585
2925
  show: boolean;
2926
+ /** Read-only canonical layout actually rendered: explicit `variant` wins, else either deprecated prop (`lines="double"` or `longerAction`) promotes to `"longer-action"`, else `"default"`. */
2927
+ get resolvedVariant(): SnackbarVariant;
2586
2928
  /** Read-only canonical tone actually rendered: explicit `tone` wins, else `type`, else `"neutral"`; deprecated aliases are resolved to `"neutral"` / `"destructive"`. */
2587
2929
  get resolvedTone(): SnackbarTone;
2588
2930
  private visible;
@@ -2600,15 +2942,25 @@ declare class RelSnackbar extends LitElement {
2600
2942
  render(): lit_html.TemplateResult<1>;
2601
2943
  }
2602
2944
 
2945
+ /** Control scale, spelled out. Figma has no Size axis on Switch; see the size ramp in the styles. */
2946
+ type SwitchSize = "small" | "medium" | "large";
2947
+ /** Alternative spellings, not deprecations — the short names work too. */
2948
+ type SwitchSizeAlias = "sm" | "md" | "lg";
2949
+ type SwitchSizeInput = SwitchSize | SwitchSizeAlias;
2950
+ /** Which side of the control the label sits on. */
2951
+ type SwitchLabelPlacement = "end" | "start";
2603
2952
  /**
2604
2953
  * Toggle switch (Figma "Switch", 5917:1174, Type=Line): a 44×20 control with a
2605
2954
  * thin 10px track and a 20px handle that slides 20px. Wraps a native checkbox
2606
2955
  * with `role="switch"`, so keyboard and screen-reader behaviour are native.
2607
- * @csspart base - The `<label>` wrapper around the hidden input and the track
2956
+ * @slot - Rich label content beside the control, as an alternative to the `label` property. Clicking it toggles the switch.
2957
+ * @csspart root - The `<label>` holding the control and its optional text; carries the `label-placement` order
2958
+ * @csspart base - The control box around the hidden input and the track
2608
2959
  * @csspart track - The thin rail; the handle is its `::before` pseudo-element
2609
- * @cssprop --rel-switch-track-width - Overall control width; default 44px
2610
- * @cssprop --rel-switch-track-height - Overall control height; default 20px
2611
- * @cssprop --rel-switch-thumb-size - Handle diameter; default 20px (the handle always travels 20px)
2960
+ * @csspart label - The text beside the control; not rendered when there is neither a `label` nor slotted content
2961
+ * @cssprop --rel-switch-track-width - Overall control width; defaults to the `size` ramp (36 / 44 / 52px)
2962
+ * @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"`
2963
+ * @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
2612
2964
  * @cssprop --rel-switch-track - Rail colour; default `var(--rel-bg-neutral-elevated-2)`
2613
2965
  * @cssprop --rel-switch-knob-on - Handle colour when checked; default `var(--rel-bg-neutral-inverse-2)`
2614
2966
  * @cssprop --rel-switch-knob-off - Handle colour when unchecked; default `var(--rel-text-static-light)`
@@ -2617,16 +2969,54 @@ declare class RelSnackbar extends LitElement {
2617
2969
  declare class RelSwitch extends LitElement {
2618
2970
  /** Whether the switch is on. Set it to control the state programmatically; user toggles update it and fire `rel-change`. */
2619
2971
  checked: boolean;
2620
- /** Disables interaction, dims the control to 50% and removes the handle shadow. */
2972
+ /**
2973
+ * Shape (Figma "Type"): `"line"` (default) is the 44x20 control with a thin
2974
+ * 40x10 rail and the handle riding on it; `"pill"` is the 44x24 filled track
2975
+ * with the handle inside it.
2976
+ * NOTE: Figma's own default is Pill. We default to `line` deliberately —
2977
+ * flipping it would restyle every existing switch — so this is a recorded
2978
+ * divergence from the design default, not an oversight.
2979
+ */
2980
+ type: "line" | "pill";
2981
+ /** Disables interaction and swaps in the dulled rail / handle fills; the handle shadow is removed. */
2621
2982
  disabled: boolean;
2622
2983
  /** Arbitrary string echoed in the `rel-change` event detail — useful when one handler serves several switches. */
2623
2984
  value: string;
2624
2985
  /** 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. */
2625
2986
  fieldAriaLabel?: string;
2987
+ /** 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. */
2988
+ label?: string;
2989
+ /**
2990
+ * Which side the label sits on: `end` (default) puts it after the control, `start` before it.
2991
+ *
2992
+ * Note this is a PHYSICAL side, not a writing-direction one, matching every
2993
+ * other placement prop in the library.
2994
+ */
2995
+ labelPlacement: SwitchLabelPlacement;
2996
+ /**
2997
+ * Control scale: `small`, `medium` (default) or `large`. `sm` / `md` / `lg`
2998
+ * are alternative spellings and stay supported — normalised to the full
2999
+ * words, not deprecated.
3000
+ *
3001
+ * Figma's Switch has axes Type / State / Enabled only, with no Size, so this
3002
+ * ramp is ours. `medium` is exactly the control that shipped before it
3003
+ * existed, so an existing switch is unchanged.
3004
+ *
3005
+ * It does NOT respond to viewport width. Picking a size per breakpoint is the
3006
+ * consuming application's decision — a control that silently resized itself
3007
+ * at some width would be undocumentable behaviour, and the breakpoint that
3008
+ * matters belongs to the layout around it, not to the switch.
3009
+ */
3010
+ size: SwitchSizeInput;
2626
3011
  private _onChange;
2627
3012
  private _onTouched;
2628
3013
  private isFocused;
2629
3014
  private isPressed;
3015
+ private hasSlottedLabel;
3016
+ /** Alternative spellings, NOT deprecations — no console notice. Normalising
3017
+ * keeps the reflected attribute canonical so the :host([size=…]) rules only
3018
+ * need the full words. Same shape as rel-chip, rel-slider and rel-radio. */
3019
+ protected willUpdate(changed: Map<string, unknown>): void;
2630
3020
  static styles: lit.CSSResult;
2631
3021
  private toggle;
2632
3022
  /** @internal Angular ControlValueAccessor bridge. */
@@ -2640,10 +3030,16 @@ declare class RelSwitch extends LitElement {
2640
3030
  private handleMouseDown;
2641
3031
  private handleMouseUp;
2642
3032
  render(): lit_html.TemplateResult<1>;
3033
+ /** Rich label content can be slotted instead of set as text; this keeps the
3034
+ * label span out of the layout entirely when neither is present, so an
3035
+ * unlabelled switch has no stray gap beside it. */
3036
+ private onSlotChange;
2643
3037
  }
2644
3038
 
2645
3039
  interface TabItem {
2646
3040
  label: string;
3041
+ /** 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. */
3042
+ icon?: string;
2647
3043
  badge?: number;
2648
3044
  badgeDot?: boolean;
2649
3045
  disabled?: boolean;
@@ -2661,6 +3057,8 @@ type TabSize = "sm" | "md" | "lg" | "small" | "medium" | "large";
2661
3057
  declare class RelTabItem extends LitElement {
2662
3058
  /** Text shown on the tab. */
2663
3059
  label: string;
3060
+ /** 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. */
3061
+ icon: string;
2664
3062
  /** Optional identifier for the tab. `<rel-tab>` does not read it; look it up from the item at `event.detail` when handling `rel-tab-change`. */
2665
3063
  value: string;
2666
3064
  /** Count shown in a small badge after the label; `0` hides the badge. */
@@ -2682,7 +3080,7 @@ declare class RelTabItem extends LitElement {
2682
3080
  * @csspart item - Each `role="tab"` element
2683
3081
  * @csspart label - The text span inside each tab
2684
3082
  * @cssprop --rel-tab-active-color - Underline colour of the active tab; default `var(--rel-bg-brand)`
2685
- * @cssprop --rel-tab-underline-height - Thickness of the active underline; default 3px
3083
+ * @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.
2686
3084
  * @cssprop --rel-tab-text-default - Label colour of the active tab; default `var(--rel-text-default)`
2687
3085
  * @cssprop --rel-tab-text-hover - Label colour while hovering an enabled tab; default `var(--rel-text-label)`
2688
3086
  * @cssprop --rel-tab-text-disabled - Label colour of a disabled tab; default `var(--rel-text-disabled)`
@@ -2882,19 +3280,38 @@ declare global {
2882
3280
  }
2883
3281
  }
2884
3282
 
2885
- /** Canonical semantic tones. */
2886
- type ToastTone = "default" | "destructive";
2887
- /** Deprecated tone spellings, all mapped to "destructive". */
2888
- type ToastToneAlias = "error" | "danger" | "critical";
3283
+ /** Canonical semantic tones (Figma "Tone"): neutral | destructive. */
3284
+ type ToastTone = "neutral" | "destructive";
3285
+ /** Deprecated spellings: "default" → neutral; "error" | "danger" | "critical" → destructive. */
3286
+ type ToastToneAlias = "default" | "error" | "danger" | "critical";
2889
3287
  type ToastToneInput = ToastTone | ToastToneAlias;
2890
3288
  /** @deprecated since 1.4.7 — use `ToastToneInput`. */
2891
3289
  type ToastType = "default" | "error";
2892
3290
  /**
2893
- * Transient bottom-centre notification: a fixed-position dark pill with a message, an optional action button and a dismiss button, shown via `show` / `open()` and hidden again after `duration` milliseconds. Use it for brief, non-blocking confirmations; use `rel-alert` for persistent messages.
3291
+ * Where the toast is anchored in the viewport. PHYSICAL sides, not logical
3292
+ * start/end: nothing else in this library adapts to writing direction, and a
3293
+ * logical model in one component would make it the odd one out.
3294
+ */
3295
+ type ToastPosition = "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
3296
+ /** Toast scale, spelled out. Figma has no Size axis on Toast; see the ramp in the styles. */
3297
+ type ToastSize = "small" | "medium" | "large";
3298
+ /** Alternative spellings, not deprecations — the short names work too. */
3299
+ type ToastSizeAlias = "sm" | "md" | "lg";
3300
+ type ToastSizeInput = ToastSize | ToastSizeAlias;
3301
+ /**
3302
+ * Transient notification: a fixed-position card with a message, an optional action button and a dismiss button, shown via `show` / `open()` and hidden again after `duration` milliseconds. Anchors to any of six viewport positions (`position`, default `bottom-center`) at three scales (`size`, default `medium`). Use it for brief, non-blocking confirmations; use `rel-alert` for persistent messages.
3303
+ *
3304
+ * STACKING. Toasts sharing a `position` stack automatically — no wrapper element
3305
+ * and no change to your markup. Up to `max-visible` (default 3) show at once,
3306
+ * newest against the anchor edge; any beyond that WAIT rather than being dropped,
3307
+ * and a waiting toast's `duration` does not start until it is actually on screen.
3308
+ * Dismissing one closes the gap for the rest. The six positions stack
3309
+ * independently. Behaviour change: five toasts at one position used to render on
3310
+ * top of each other; now three show and two wait.
2894
3311
  * @csspart base - The `role="status"` toast container that carries the tone class
2895
3312
  * @csspart label - The message text span
2896
3313
  * @csspart action - The action button; rendered only when `actionlabel` is set
2897
- * @csspart close-button - The dismiss (✕) button
3314
+ * @csspart close-button - The dismiss button; rendered while `dismissible` is true
2898
3315
  * @cssprop --rel-toast-z - `z-index` of the fixed host (default `9000`); raise it when a modal or drawer layer covers the toast
2899
3316
  * @cssprop --rel-z-toast - Deprecated alias of `--rel-toast-z`, still read as its fallback
2900
3317
  */
@@ -2902,12 +3319,12 @@ declare class RelToast extends LitElement {
2902
3319
  /** Text of the toast. It is also announced to screen readers through the shared live-region announcer each time `show` becomes true. */
2903
3320
  message: string;
2904
3321
  /**
2905
- * Semantic tone: `"default"` (dark neutral pill) | `"destructive"` (danger fill, announced assertively). `"error"`, `"danger"` and `"critical"` are deprecated aliases of `"destructive"` and log a one-time console notice. Takes precedence over the deprecated `type` prop.
3322
+ * 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.
2906
3323
  */
2907
3324
  tone?: ToastToneInput;
2908
- /** @deprecated Deprecated since 1.4.7 — alias of `tone`; use `tone`. Same values as `tone`; `type="error"` maps to `tone="destructive"`. Ignored when `tone` is set. Logs a one-time console notice. */
3325
+ /** @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. */
2909
3326
  type?: ToastToneInput;
2910
- /** Read-only canonical tone actually rendered: explicit `tone` wins, else `type`, else `"default"`; deprecated aliases are resolved to `"destructive"`. */
3327
+ /** Read-only canonical tone actually rendered: explicit `tone` wins, else `type`, else `"neutral"`; deprecated aliases are resolved to `"neutral"` / `"destructive"`. */
2911
3328
  get resolvedTone(): ToastTone;
2912
3329
  /** 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. */
2913
3330
  actionLabel?: string;
@@ -2915,10 +3332,54 @@ declare class RelToast extends LitElement {
2915
3332
  duration: number;
2916
3333
  /** 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. */
2917
3334
  show: boolean;
3335
+ /** 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. */
3336
+ dismissible: boolean;
3337
+ /**
3338
+ * Where the toast is anchored: `bottom-center` (default, unchanged from
3339
+ * before this prop existed), `bottom-left`, `bottom-right`, `top-left`,
3340
+ * `top-center` or `top-right`. Top anchors also animate in from the top edge
3341
+ * rather than upward from the bottom.
3342
+ *
3343
+ * Figma's Toast has axes Content / Show Button / Show Close Button / Tone and
3344
+ * no position, so this axis is ours.
3345
+ */
3346
+ position: ToastPosition;
3347
+ /**
3348
+ * Toast scale: `small`, `medium` (default, unchanged) or `large`. `sm` / `md`
3349
+ * / `lg` are alternative spellings and stay supported — normalised to the
3350
+ * full words, not deprecated. Scales the padding, the inner gap, the message
3351
+ * type role and the close icon. Figma has no Size axis on Toast.
3352
+ */
3353
+ size: ToastSizeInput;
3354
+ /**
3355
+ * How many toasts may be on screen at once AT THIS POSITION. Further ones
3356
+ * wait their turn rather than being dropped — a notification the user never
3357
+ * saw is worse than a late one — and each toast's `duration` only starts
3358
+ * once it actually becomes visible, so a queued message cannot expire unseen.
3359
+ * Default 3; unbounded stacking just fills the viewport.
3360
+ */
3361
+ maxVisible: number;
2918
3362
  private visible;
2919
3363
  private _dismissTimer;
3364
+ /** The stack this instance is currently registered in, so a `position`
3365
+ * change while shown can leave the old stack before joining the new one. */
3366
+ private _registeredAt;
2920
3367
  static styles: lit.CSSResult;
2921
3368
  willUpdate(changed: Map<string, unknown>): void;
3369
+ private joinStack;
3370
+ private leaveStack;
3371
+ /**
3372
+ * Called by the reflow with this instance's admission state. The
3373
+ * auto-dismiss timer starts HERE, on becoming visible — not when `show` is
3374
+ * set — so a toast waiting its turn cannot expire before anyone has seen it.
3375
+ */
3376
+ private _setVisible;
3377
+ /** Offset from the anchor edge, written by the reflow. Index 0 gets no
3378
+ * transform at all, so a lone toast is untouched by the stacking code. */
3379
+ private _setStackOffset;
3380
+ /** Measured height of the rendered card, for the stack arithmetic. */
3381
+ private get _cardHeight();
3382
+ private get anchorsTop();
2922
3383
  disconnectedCallback(): void;
2923
3384
  /** Shows the toast: sets `show = true`, which starts the auto-dismiss timer and announces `message`. */
2924
3385
  open(): void;
@@ -2930,21 +3391,31 @@ declare class RelToast extends LitElement {
2930
3391
 
2931
3392
  type TooltipPosition = "above" | "below" | "left" | "right";
2932
3393
  type TooltipType = "single-line" | "multi-line";
3394
+ /**
3395
+ * Bubble appearance. `inverse` is the original look — an inverse-surface bubble
3396
+ * that is dark-on-light in light mode and light-on-dark in dark mode.
3397
+ * `neutral` is a light-surface bubble, the same sense the word already carries
3398
+ * on rel-toast, where `tone="neutral"` is likewise the light surface.
3399
+ */
3400
+ type TooltipTone = "inverse" | "neutral";
2933
3401
  /**
2934
3402
  * 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.
2935
3403
  * @slot - The trigger element (one focusable element such as a button or icon button). It receives `aria-describedby` pointing at the bubble.
2936
3404
  * @csspart panel - The tooltip bubble (`role="tooltip"`): background, padding, radius, shadow and max-width.
2937
3405
  * @csspart indicator - The triangular tail pointing at the trigger, rendered only when `show-tail` is set.
3406
+ * @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.
2938
3407
  */
2939
3408
  declare class RelTooltip extends LitElement {
2940
3409
  /** Plain-text content of the bubble. Empty text still renders an (empty) bubble on hover, so always set it. */
2941
3410
  text: string;
2942
- /** Side of the trigger the bubble appears on, 6px away and centred: `above`, `below`, `left` or `right`. No collision detection — pick a side with room in the viewport. */
3411
+ /** 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. */
2943
3412
  position: TooltipPosition;
2944
3413
  /** Renders the triangular tail (`indicator` part) on the bubble's edge facing the trigger. */
2945
3414
  showTail: boolean;
2946
- /** Figma "Type": `single-line` keeps the text on one line and sizes to content; `multi-line` wraps inside a 220px max-width bubble. */
3415
+ /** Figma "Type": `single-line` keeps the text on one line and sizes to content; `multi-line` wraps inside a 200px max-width bubble. */
2947
3416
  type: TooltipType;
3417
+ /** 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. */
3418
+ tone: TooltipTone;
2948
3419
  private uniqueId;
2949
3420
  static styles: lit.CSSResult;
2950
3421
  connectedCallback(): void;
@@ -3035,7 +3506,8 @@ interface UploadState {
3035
3506
  * @fires rel-file-error - Fired when one file's `uploader` rejects in self-driving mode. Detail: `{ id: string, file: File, error: string }`.
3036
3507
  * @fires rel-complete - Fired when the whole self-driving batch settles without being cancelled. Detail: `{ succeeded: UploadFile[], failed: UploadFile[] }`.
3037
3508
  * @fires rel-state - Fired when the derived `state` snapshot changes (and once after connecting). Detail: `UploadState`.
3038
- * @cssprop --rel-upload-browse-hover-bg - Background of the Browse button on hover; falls back to `--rel-action-neutral-outline-hover`, then `--rel-bg-surface-elevated`
3509
+ * @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.
3510
+ * @csspart browse - The `rel-button` that opens the file picker (Figma "Browse Button": Size=Medium, Style=Secondary).
3039
3511
  */
3040
3512
  declare class RelUpload extends LitElement {
3041
3513
  /** Comma-separated accept list, e.g. ".pdf,.png,.jpg" or "image/*". */
@@ -3319,4 +3791,4 @@ type Politeness = "polite" | "assertive";
3319
3791
  */
3320
3792
  declare function announce(message: string, politeness?: Politeness): void;
3321
3793
 
3322
- export { type AlertTone, type AlertToneAlias, type AlertToneInput, type AutocompleteFilter, type AutocompleteItem, type AutocompleteSearchFn, type BindShortcutOptions, type BreadcrumbItem, type ButtonTone, type ButtonVariant, type ChipBorder, type ChipSize, type ChipTone, type ChipVariant, type ContainerBackground, type ContainerSurface, type DrawerCloseReason, type DrawerSide, type FieldLabelStyle, FocusTrap, type KbdSize, type LabelPlacement, type MenuItem, type MenuSize, type MenuType, type MenubarType, type ModalSide, type ModalSize, type ModalVariant, type Orientation, type OtpInputMode, type OtpSize, type OtpType, type ProgressMode, type RadioOption, 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 SliderType, type SnackbarLines, type SnackbarTone, type SnackbarToneAlias, type SnackbarToneInput, type SnackbarType, type Step, type StepStatus, type StepperSize, type StepperType, type TabItem, type TabSize, type TextAlign, type TextFieldPrefixStyle, type TextFieldSize, type TextFieldState, type TextFieldVariant, type ToastTone, type ToastToneAlias, type ToastToneInput, type ToastType, type TooltipPosition, 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 };
3794
+ 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 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 };