@ds-gn/ui 0.10.0 → 0.11.1

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 (81) hide show
  1. package/README.md +4 -4
  2. package/custom-elements.json +5511 -2144
  3. package/dist/common/controllers/OverlayDismissController.d.ts +52 -0
  4. package/dist/common/controllers/OverlayPositionController.d.ts +168 -0
  5. package/dist/common/controllers/RovingFocusController.d.ts +99 -0
  6. package/dist/common/helpers/field-control.d.ts +35 -0
  7. package/dist/common/helpers/listbox-navigation.d.ts +50 -0
  8. package/dist/common/props/size/size.props.d.ts +11 -0
  9. package/dist/common/styles/overlay-panel.styles.d.ts +55 -0
  10. package/dist/components/Button/src/Button.d.ts +20 -0
  11. package/dist/components/Button/src/Button.js +28 -0
  12. package/dist/components/DropdownMenu/src/DropdownMenu.d.ts +150 -0
  13. package/dist/components/DropdownMenu/src/DropdownMenu.js +1550 -0
  14. package/dist/components/DropdownMenu/src/DropdownMenu.playground.d.ts +2 -0
  15. package/dist/components/DropdownMenu/src/DropdownMenu.props.d.ts +47 -0
  16. package/dist/components/DropdownMenu/src/DropdownMenu.props.js +54 -0
  17. package/dist/components/DropdownMenu/src/DropdownMenu.styles.d.ts +20 -0
  18. package/dist/components/Field/src/Field.d.ts +39 -0
  19. package/dist/components/Field/src/Field.js +203 -0
  20. package/dist/components/Field/src/Field.playground.d.ts +2 -0
  21. package/dist/components/Field/src/Field.styles.d.ts +6 -0
  22. package/dist/components/Icon/src/Icon.d.ts +5 -0
  23. package/dist/components/Icon/src/Icon.js +3 -0
  24. package/dist/components/Icon/src/icons/arrow-down.d.ts +1 -0
  25. package/dist/components/Icon/src/icons/arrow-down.js +6 -0
  26. package/dist/components/Icon/src/icons/arrow-up.d.ts +1 -0
  27. package/dist/components/Icon/src/icons/arrow-up.js +6 -0
  28. package/dist/components/Icon/src/icons/chevrons-up-down.d.ts +1 -0
  29. package/dist/components/Icon/src/icons/chevrons-up-down.js +6 -0
  30. package/dist/components/Icon/src/icons/cookie.d.ts +1 -0
  31. package/dist/components/Icon/src/icons/cookie.js +6 -0
  32. package/dist/components/Icon/src/icons/icon-names.d.ts +1 -1
  33. package/dist/components/Icon/src/icons/icon-names.js +4 -0
  34. package/dist/components/Icon/src/icons/index.d.ts +4 -0
  35. package/dist/components/Icon/src/icons/index.js +4 -0
  36. package/dist/components/Input/src/Input.d.ts +37 -0
  37. package/dist/components/Input/src/Input.js +300 -143
  38. package/dist/components/Input/src/Input.props.d.ts +2 -1
  39. package/dist/components/Input/src/Input.props.js +12 -6
  40. package/dist/components/Input/src/Input.styles.d.ts +12 -4
  41. package/dist/components/Modal/src/Modal.d.ts +1 -0
  42. package/dist/components/Modal/src/Modal.js +1 -0
  43. package/dist/components/Nav/src/Nav.js +2 -1
  44. package/dist/components/NavLink/src/NavLink.d.ts +14 -30
  45. package/dist/components/NavLink/src/NavLink.js +201 -65
  46. package/dist/components/Select/src/Select.d.ts +157 -0
  47. package/dist/components/Select/src/Select.js +1766 -0
  48. package/dist/components/Select/src/Select.playground.d.ts +2 -0
  49. package/dist/components/Select/src/Select.props.d.ts +19 -0
  50. package/dist/components/Select/src/Select.props.js +40 -0
  51. package/dist/components/Select/src/Select.styles.d.ts +30 -0
  52. package/dist/components/Table/src/Table.d.ts +93 -0
  53. package/dist/components/Table/src/Table.js +1105 -0
  54. package/dist/components/Table/src/Table.playground.d.ts +2 -0
  55. package/dist/components/Table/src/Table.props.d.ts +53 -0
  56. package/dist/components/Table/src/Table.props.js +18 -0
  57. package/dist/components/Table/src/Table.styles.d.ts +33 -0
  58. package/dist/components/Toaster/src/Toaster.d.ts +174 -0
  59. package/dist/components/Toaster/src/Toaster.js +1489 -0
  60. package/dist/components/Toaster/src/Toaster.playground.d.ts +2 -0
  61. package/dist/components/Toaster/src/Toaster.props.d.ts +14 -0
  62. package/dist/components/Toaster/src/Toaster.props.js +81 -0
  63. package/dist/components/Toaster/src/Toaster.styles.d.ts +52 -0
  64. package/dist/components/Toaster/src/toast.d.ts +115 -0
  65. package/dist/components/Toaster/src/toast.js +74 -0
  66. package/dist/playground/types.d.ts +27 -0
  67. package/dist/playground-manifest.d.ts +3 -2
  68. package/dist/playground-manifest.js +507 -7
  69. package/dist/react/DropdownMenu.d.ts +8 -0
  70. package/dist/react/DropdownMenu.js +13 -0
  71. package/dist/react/Field.d.ts +6 -0
  72. package/dist/react/Field.js +13 -0
  73. package/dist/react/Select.d.ts +8 -0
  74. package/dist/react/Select.js +13 -0
  75. package/dist/react/Table.d.ts +8 -0
  76. package/dist/react/Table.js +13 -0
  77. package/dist/react/Toaster.d.ts +8 -0
  78. package/dist/react/Toaster.js +13 -0
  79. package/dist/react/index.d.ts +6 -0
  80. package/dist/react/index.js +6 -0
  81. package/package.json +54 -4
@@ -0,0 +1,52 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /** What dismissed an overlay. */
3
+ export type OverlayDismissReason = 'escape' | 'outside';
4
+ export interface OverlayDismissOptions {
5
+ /** Whether there is an open overlay for Escape to close. */
6
+ isOpen: () => boolean;
7
+ /**
8
+ * Close the overlay. Runs before focus is returned, so a host can record the
9
+ * dismissal first (e.g. to keep a focus-to-open trigger from re-opening).
10
+ */
11
+ onDismiss: (reason: OverlayDismissReason) => void;
12
+ /** Where focus goes after an Escape dismissal — usually the trigger. */
13
+ returnFocusTo?: () => HTMLElement | null | undefined;
14
+ }
15
+ /**
16
+ * Dismisses an overlay the two standard ways: Escape (closing it and returning
17
+ * focus to its trigger) and a pointer press outside the host.
18
+ *
19
+ * **Escape** is not a global listener: the host routes its own `keydown`
20
+ * through `handleKeydown`, so the key only counts while focus is inside the
21
+ * component, and a host with a richer key handler (a listbox) decides where in
22
+ * that handler Escape is checked. It returns whether it dismissed, so the host
23
+ * can `preventDefault` or stop there.
24
+ *
25
+ * **Outside press** is a `pointerdown` listener on the document, present only
26
+ * between `start()` and `stop()` — a host decides when an outside press
27
+ * should matter (a nav dropdown only once pinned; a listbox whenever open).
28
+ * The press is tested against the event's composed path, so presses inside the
29
+ * host's shadow root count as inside. Like `LayoutChangeController`, the
30
+ * intent survives a disconnect and the listener returns on reconnect.
31
+ */
32
+ export declare class OverlayDismissController implements ReactiveController {
33
+ private host;
34
+ private options;
35
+ private listening;
36
+ private wantsOutside;
37
+ constructor(host: ReactiveControllerHost & HTMLElement, options: OverlayDismissOptions);
38
+ /** Start closing on a pointer press outside the host. Idempotent. */
39
+ start(): void;
40
+ /** Stop closing on outside presses. Idempotent. */
41
+ stop(): void;
42
+ /**
43
+ * Dismiss on Escape while open, then return focus. Returns `true` when the
44
+ * event dismissed the overlay.
45
+ */
46
+ handleKeydown(event: KeyboardEvent): boolean;
47
+ hostConnected(): void;
48
+ hostDisconnected(): void;
49
+ private handleDocumentPointerDown;
50
+ private listen;
51
+ private unlisten;
52
+ }
@@ -0,0 +1,168 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /**
3
+ * Breathing room kept between an overlay and the viewport edge.
4
+ */
5
+ export declare const overlayViewportGutter = 16;
6
+ /** Which edge of its anchor an overlay lines up with. */
7
+ export type OverlayAlign = 'start' | 'end';
8
+ /** Vertical side of the anchor an overlay hangs from. */
9
+ export type OverlaySide = 'bottom' | 'top';
10
+ /**
11
+ * Where an overlay hangs: its side of the anchor, and the anchor edge it lines
12
+ * up with before any shift (`-end` for a "⋯" trigger at the end of a row).
13
+ */
14
+ export type OverlayPlacement = `${OverlaySide}-${OverlayAlign}`;
15
+ /**
16
+ * Where to put an overlay that is `position: fixed` against the viewport —
17
+ * for example one shown in the top layer with the Popover API, which escapes
18
+ * every ancestor's overflow clipping and stacking context but is no longer
19
+ * laid out relative to its anchor.
20
+ */
21
+ export interface OverlayViewportPosition {
22
+ /** Distance from the viewport's left edge, shift included. */
23
+ left: number;
24
+ /** Distance from the viewport's top edge; set when hanging below. */
25
+ top: number | null;
26
+ /** Distance from the viewport's bottom edge; set when flipped above. */
27
+ bottom: number | null;
28
+ /** The anchor's width, for overlays that match it. */
29
+ anchorWidth: number;
30
+ }
31
+ export interface OverlayPosition {
32
+ /** The side the overlay hangs from, after any vertical flip. */
33
+ placement: OverlayPlacement;
34
+ /**
35
+ * Horizontal shift, measured from the start-aligned position under (or
36
+ * above) the anchor. See `computeOverlayShift`. This is all an overlay
37
+ * positioned against its anchor (`position: absolute`) needs.
38
+ */
39
+ shift: number;
40
+ /** The same position in viewport coordinates, for a `fixed` overlay. */
41
+ viewport: OverlayViewportPosition;
42
+ }
43
+ export interface OverlaySize {
44
+ width: number;
45
+ height: number;
46
+ }
47
+ type AnchorRect = Pick<DOMRect, 'left' | 'width' | 'top' | 'bottom'>;
48
+ /** Room between the chosen panel edge and the viewport gutter. */
49
+ export declare const computeOverlayAvailableHeight: (viewport: OverlayViewportPosition, viewportHeight: number, gutter?: number) => number;
50
+ /**
51
+ * Viewport coordinates for a `fixed` overlay. A flipped overlay is pinned by
52
+ * its bottom edge, so it stays against the anchor however tall it renders.
53
+ */
54
+ export declare const computeViewportPosition: (anchor: AnchorRect, { placement, shift }: Pick<OverlayPosition, "placement" | "shift">, viewportHeight: number, offset?: number) => OverlayViewportPosition;
55
+ /**
56
+ * Horizontal shift for an overlay, measured from the start-aligned position
57
+ * under its anchor. Hanging from the anchor's start edge is the default; if
58
+ * that would spill past the viewport, the overlay flips to end on the anchor's
59
+ * far edge instead. Only when *that* would leave the viewport too — an overlay
60
+ * wider than the room on either side of its anchor — does it give up the
61
+ * anchor and sit on the gutter, where a fluid width has already shrunk it to
62
+ * fit.
63
+ *
64
+ * `align: 'end'` mirrors this: the overlay ends on the anchor's end edge,
65
+ * flips to start-aligned when that would spill past the left gutter, and sits
66
+ * on the right gutter when neither fits.
67
+ */
68
+ export declare const computeOverlayShift: (anchor: AnchorRect, overlayWidth: number, viewportWidth: number, gutter?: number, align?: OverlayAlign) => number;
69
+ /**
70
+ * Vertical side for an overlay. Below the anchor is the default; it flips
71
+ * above only when it doesn't fit below and does fit above. When it fits on
72
+ * neither side it takes whichever has more room, so a long list scrolls inside
73
+ * the larger space rather than the smaller one.
74
+ */
75
+ export declare const computeOverlayPlacement: (anchor: AnchorRect, overlayHeight: number, viewportHeight: number, { gutter, offset, align, }?: {
76
+ gutter?: number;
77
+ offset?: number;
78
+ align?: OverlayAlign;
79
+ }) => OverlayPlacement;
80
+ /**
81
+ * Size an overlay renders at. A closed overlay is usually `display: none`, so
82
+ * lay it out just long enough to read it — the browser cannot paint between
83
+ * the two writes, so nothing flashes. The overlay's own inline style is
84
+ * restored afterwards.
85
+ */
86
+ export declare const measureOverlay: (overlay: HTMLElement) => OverlaySize;
87
+ export interface OverlayPositionOptions {
88
+ /** The element the overlay hangs from. Defaults to the host. */
89
+ anchor?: () => Element | null | undefined;
90
+ /** The overlay element to measure. */
91
+ overlay: () => HTMLElement | null | undefined;
92
+ /** Write a computed position to the overlay (CSS custom properties, state). */
93
+ apply: (position: OverlayPosition) => void;
94
+ /** Undo `apply` when the overlay closes. */
95
+ clear?: () => void;
96
+ /**
97
+ * Runs before the overlay is measured, with the anchor's box. A `fixed`
98
+ * overlay that sizes itself from its anchor (rather than from a containing
99
+ * block) sets that size here, so the measurement sees its real width.
100
+ */
101
+ beforeMeasure?: (anchor: DOMRect) => void;
102
+ /** Viewport gutter in px. Defaults to `overlayViewportGutter`. */
103
+ gutter?: number;
104
+ /** Gap between anchor and overlay, counted against the room on each side. */
105
+ offset?: number;
106
+ /**
107
+ * Flip above the anchor when there isn't room below. Off, the overlay always
108
+ * hangs below.
109
+ */
110
+ flip?: boolean;
111
+ /**
112
+ * The anchor edge the overlay lines up with: `start` (default) or `end`.
113
+ * Read on every measurement, so a host can change it while open.
114
+ */
115
+ align?: () => OverlayAlign;
116
+ /**
117
+ * Also re-measure on scroll (any scroll container, captured) and window
118
+ * resize, coalesced to one measurement per frame. Width changes are always
119
+ * tracked through the shared layout observer; this adds the height and
120
+ * scroll changes a vertical flip depends on.
121
+ */
122
+ trackViewport?: boolean;
123
+ }
124
+ /**
125
+ * Positions an overlay against its anchor and the viewport while it is open:
126
+ * start-aligned (or, with `align`, end-aligned) under the anchor, shifted to
127
+ * stay inside the viewport gutter,
128
+ * and optionally flipped above when there isn't room below.
129
+ *
130
+ * Each position is reported two ways: as a `shift` for an overlay laid out
131
+ * against its anchor (`position: absolute` in a shared containing block), and
132
+ * as `viewport` coordinates for a `fixed` overlay, such as one in the top
133
+ * layer. A top-layer overlay should also set `trackViewport`, since it no
134
+ * longer moves with the page on its own.
135
+ *
136
+ * `start()` measures and subscribes to layout changes; `stop()` unsubscribes
137
+ * and calls `clear`, so the next open always starts from a measurement rather
138
+ * than a stale one. `update()` re-measures on demand — call it before opening
139
+ * if the overlay must land in place on its first frame.
140
+ *
141
+ * The subscription is the shared observer in `helpers/layout-change.ts` (the
142
+ * document width and the host's own box), plus scroll/resize listeners when
143
+ * `trackViewport` is on. Like `LayoutChangeController`, the intent survives a
144
+ * disconnect: a host moved in the DOM while open resubscribes on reconnect.
145
+ */
146
+ export declare class OverlayPositionController implements ReactiveController {
147
+ private host;
148
+ private options;
149
+ private unsubscribe?;
150
+ private wantsUpdates;
151
+ private frame?;
152
+ private _position?;
153
+ constructor(host: ReactiveControllerHost & HTMLElement, options: OverlayPositionOptions);
154
+ /** The last applied position, or `undefined` while stopped. */
155
+ get position(): OverlayPosition | undefined;
156
+ /** Measure now and keep measuring on layout changes. Idempotent. */
157
+ start(): void;
158
+ /** Stop measuring and clear the applied position. Idempotent. */
159
+ stop(): void;
160
+ /** Measure and apply the position once. */
161
+ update: () => void;
162
+ hostConnected(): void;
163
+ hostDisconnected(): void;
164
+ private scheduleUpdate;
165
+ private subscribe;
166
+ private unsubscribeAll;
167
+ }
168
+ export {};
@@ -0,0 +1,99 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /** Which arrow keys move between items. */
3
+ export type RovingFocusOrientation = 'vertical' | 'horizontal' | 'both';
4
+ export interface RovingFocusOptions {
5
+ /**
6
+ * The items, in order — every element that could take focus, including any
7
+ * the controller should skip. Read on each key press, so a list that
8
+ * re-renders is never stale.
9
+ */
10
+ items: () => readonly HTMLElement[];
11
+ /**
12
+ * Items arrow keys and typeahead pass over, such as separators. Defaults to
13
+ * `role="separator"`. Disabled items are *not* skipped: in the APG menu,
14
+ * toolbar and tablist patterns they stay focusable so they can be
15
+ * discovered, and the host refuses to activate them.
16
+ */
17
+ isSkipped?: (item: HTMLElement) => boolean;
18
+ /** Arrow keys that move focus. Defaults to `vertical` (↑ ↓). */
19
+ orientation?: RovingFocusOrientation;
20
+ /** Whether moving past either end wraps around. Defaults to `true`. */
21
+ wrap?: boolean;
22
+ /** Text matched by typeahead. Defaults to the trimmed `textContent`. */
23
+ labelOf?: (item: HTMLElement) => string;
24
+ /** Whether typing a character moves to a matching item. Defaults to `true`. */
25
+ typeahead?: boolean;
26
+ /** How long typed characters accumulate. Defaults to `typeaheadTimeout`. */
27
+ typeaheadTimeout?: number;
28
+ /** Runs after focus lands on an item through the controller or a `focusin`. */
29
+ onFocusChange?: (item: HTMLElement, index: number) => void;
30
+ }
31
+ /**
32
+ * Roving focus over a group of items — the keyboard model shared by the
33
+ * WAI-ARIA APG menu, toolbar and tablist patterns. DOM focus moves between the
34
+ * items themselves (unlike a listbox's `aria-activedescendant`), and exactly
35
+ * one item is the group's tab stop (`tabindex="0"`, the rest `-1`).
36
+ *
37
+ * Keys are not a global listener: the host routes its own `keydown` through
38
+ * `handleKeydown`, which returns whether it handled the key so the host can
39
+ * `preventDefault` or stop there — the same contract as
40
+ * `OverlayDismissController`. Arrow keys (per `orientation`), Home and End
41
+ * move; printable characters run typeahead (first-letter match, refined while
42
+ * typing within the timeout). Space is only taken while a typeahead search is
43
+ * active, so a host can still use it to activate.
44
+ *
45
+ * Activation is the host's job, so this controller has no notion of
46
+ * "disabled": disabled items are focused like any other, and the host checks
47
+ * `aria-disabled` before acting.
48
+ *
49
+ * Focus that arrives some other way — a click, or a host focusing an item on
50
+ * hover — is picked up through `focusin`, so the tab stop and `activeIndex`
51
+ * always follow the focused item.
52
+ */
53
+ export declare class RovingFocusController implements ReactiveController {
54
+ private host;
55
+ private options;
56
+ private readonly typeahead;
57
+ private current;
58
+ constructor(host: ReactiveControllerHost & HTMLElement, options: RovingFocusOptions);
59
+ /** Index of the current item among `items()`, or `-1`. */
60
+ get activeIndex(): number;
61
+ /** The current item — the tab stop — or `null`. */
62
+ get activeItem(): HTMLElement | null;
63
+ /** Whether characters are being collected into a typeahead search. */
64
+ get typeaheadActive(): boolean;
65
+ /** Focus the first item that is not skipped. Returns it, or `null`. */
66
+ focusFirst(): HTMLElement | null;
67
+ /** Focus the last item that is not skipped. Returns it, or `null`. */
68
+ focusLast(): HTMLElement | null;
69
+ /** Focus `item` (ignored if it is skipped or not an item). */
70
+ focusItem(item: HTMLElement): HTMLElement | null;
71
+ /**
72
+ * Make one item the tab stop (the current one, else the first) and every
73
+ * other item `tabindex="-1"`. Skipped items get no `tabindex` at all: a
74
+ * focusable separator reads as a slider. Call after the items render.
75
+ */
76
+ syncTabStops(): void;
77
+ /** Forget the current item and any typeahead in progress. */
78
+ reset(): void;
79
+ /**
80
+ * Move focus for an arrow, Home, End or typeahead key. Returns `true` when
81
+ * the key was handled.
82
+ */
83
+ handleKeydown(event: KeyboardEvent): boolean;
84
+ hostConnected(): void;
85
+ hostDisconnected(): void;
86
+ private navigable;
87
+ private isTypeaheadKey;
88
+ /**
89
+ * The item a navigation key moves to; `null` when the key is handled but
90
+ * focus stays (no wrap at an end), `undefined` when it is not a navigation
91
+ * key.
92
+ */
93
+ private targetFor;
94
+ /** One item on from the current one, wrapping if allowed. */
95
+ private step;
96
+ private focusAt;
97
+ private setCurrent;
98
+ private handleFocusIn;
99
+ }
@@ -0,0 +1,35 @@
1
+ import type { SizeType } from '../props/size/size.props.js';
2
+ /**
3
+ * The labelling contract between `dsgn-field` and a form control.
4
+ *
5
+ * `aria-labelledby` and `aria-describedby` cannot reference IDs across shadow
6
+ * roots, so Field cannot point its slotted control at a label it renders
7
+ * itself. Instead Field writes these properties onto the control, and the
8
+ * control renders the label, hint and error inside its own shadow root, next
9
+ * to the native element they describe, and wires the ARIA there.
10
+ *
11
+ * A control adopts the contract by declaring these reactive properties and
12
+ * calling `markFieldControl` on its class.
13
+ */
14
+ export interface FieldControl extends HTMLElement {
15
+ label: string;
16
+ hint: string;
17
+ error: string;
18
+ required: boolean;
19
+ /** Set by Field from a non-empty `error`; a control may also set it alone. */
20
+ invalid: boolean;
21
+ size: SizeType;
22
+ }
23
+ /**
24
+ * Brands a control class as implementing `FieldControl`. Kept off the class
25
+ * body so the brand never surfaces as a public member in the custom elements
26
+ * manifest or the generated React wrapper.
27
+ */
28
+ export declare const markFieldControl: (ctor: CustomElementConstructor) => void;
29
+ export declare const isFieldControl: (element: Element) => element is FieldControl;
30
+ /**
31
+ * `aria-describedby` value for a control: the error is read before the hint,
32
+ * and only IDs of rendered elements are included. `undefined` when there is
33
+ * nothing to reference, so the attribute is omitted rather than left empty.
34
+ */
35
+ export declare const describedBy: (...ids: Array<string | false | null | undefined>) => string | undefined;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Keyboard navigation over a flat list of items, some of them disabled — the
3
+ * shared part of the WAI-ARIA APG listbox and menu patterns. Indices are into
4
+ * the full list; `-1` means "no item".
5
+ */
6
+ export interface NavigableItem {
7
+ disabled?: boolean;
8
+ }
9
+ /**
10
+ * The next enabled index from `from` in direction `dir`, without wrapping.
11
+ * Stays on `from` when there is no enabled item that way.
12
+ */
13
+ export declare const stepEnabled: (items: readonly NavigableItem[], from: number, dir: 1 | -1) => number;
14
+ /** The first enabled index, or `-1`. */
15
+ export declare const firstEnabled: (items: readonly NavigableItem[]) => number;
16
+ /** The last enabled index, or `-1`. */
17
+ export declare const lastEnabled: (items: readonly NavigableItem[]) => number;
18
+ /**
19
+ * Whether a key event types a character, so it should feed typeahead rather
20
+ * than act as a command.
21
+ */
22
+ export declare const isPrintableKey: (event: KeyboardEvent) => boolean;
23
+ /** How long typed characters keep accumulating into one search. */
24
+ export declare const typeaheadTimeout = 500;
25
+ /**
26
+ * Accumulates typed characters for typeahead and finds the matching item.
27
+ *
28
+ * A single character searches from the item after `from`, so pressing the
29
+ * same letter repeatedly cycles through the items that start with it. Longer
30
+ * buffers search from `from` itself, so typing "sv" refines the match on "s"
31
+ * instead of skipping past it. The search wraps and skips disabled items.
32
+ */
33
+ export declare class TypeaheadBuffer {
34
+ private readonly timeout;
35
+ private buffer;
36
+ private timer?;
37
+ constructor(timeout?: number);
38
+ /** Whether characters are currently being collected. */
39
+ get active(): boolean;
40
+ /**
41
+ * Add `char` to the buffer and return the matching index, or `-1`.
42
+ * `from` is the current item; `-1` starts the search at the top. With
43
+ * `cycle: false` the search always includes `from` itself, which is what a
44
+ * closed select wants: typing the selected option's letter keeps it.
45
+ */
46
+ search<T extends NavigableItem>(items: readonly T[], char: string, from: number, labelOf: (item: T) => string, { cycle }?: {
47
+ cycle?: boolean;
48
+ }): number;
49
+ clear(): void;
50
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The control size scale shared by form controls and `dsgn-field`, so a Field
3
+ * and the control inside it always agree on what `sm`, `md` and `lg` mean.
4
+ */
5
+ export declare const sizeValues: {
6
+ readonly SM: "sm";
7
+ readonly MD: "md";
8
+ readonly LG: "lg";
9
+ };
10
+ export type SizeType = (typeof sizeValues)[keyof typeof sizeValues];
11
+ export declare const sizeProps: import("lit").PropertyDeclaration<unknown, unknown>;
@@ -0,0 +1,55 @@
1
+ import type { OverlayPlacement } from '../controllers/OverlayPositionController.js';
2
+ /**
3
+ * Class lists shared by the floating panels — `dsgn-select`'s listbox and
4
+ * `dsgn-dropdown-menu`'s menu — and the rows inside them, so the two read as
5
+ * one overlay layer.
6
+ *
7
+ * A component that imports this module has it added to its Tailwind sources
8
+ * by the build (see `sharedStyleSources` in `build/build-component.ts`), so
9
+ * every class named here must stay a literal string.
10
+ */
11
+ /**
12
+ * Position written on the host by the position controller and inherited by
13
+ * the panel (a top-layer element still inherits through the DOM tree).
14
+ * `shift` places the anchored fallback; the rest place a top-layer panel in
15
+ * viewport coordinates.
16
+ */
17
+ export declare const overlayPositionProperties: {
18
+ readonly shift: "--dsgn-overlay-shift";
19
+ readonly left: "--dsgn-overlay-left";
20
+ readonly top: "--dsgn-overlay-top";
21
+ readonly bottom: "--dsgn-overlay-bottom";
22
+ readonly width: "--dsgn-overlay-width";
23
+ readonly maxHeight: "--dsgn-overlay-max-height";
24
+ };
25
+ /** Gap between trigger and panel; matches `calc(100% + 4px)` below. */
26
+ export declare const overlayPanelOffset = 4;
27
+ export interface OverlayPanelState {
28
+ /** Displayed — stays true through the exit animation. */
29
+ visible: boolean;
30
+ /** Open — false while the exit animation runs. */
31
+ open: boolean;
32
+ /** Shown in the top layer rather than anchored in the component. */
33
+ topLayer: boolean;
34
+ }
35
+ /**
36
+ * The panel surface: popover background, border, radius, padding, shadow and
37
+ * item gap, positioned for its mode and placement. Width is the caller's.
38
+ */
39
+ export declare const overlayPanelClasses: ({ visible, open, topLayer }: OverlayPanelState, placement: OverlayPlacement) => string[];
40
+ /**
41
+ * A row in a panel: layout, radius and the shared highlight. Mouse hover and
42
+ * keyboard highlight are the same state — hovering moves the active row — so
43
+ * one style covers both. Height, text size and gap are the caller's.
44
+ *
45
+ * `tone` replaces the enabled text colour and highlight fill (a destructive
46
+ * menu item), so two colour utilities never compete on one row.
47
+ */
48
+ export declare const overlayItemClasses: ({ active, disabled, tone, }: {
49
+ active: boolean;
50
+ disabled: boolean;
51
+ tone?: {
52
+ text: string;
53
+ activeFill: string;
54
+ };
55
+ }) => string[];
@@ -16,6 +16,12 @@ export declare class Button extends Base {
16
16
  * submission the way a native submit button does.
17
17
  */
18
18
  static formAssociated: boolean;
19
+ /**
20
+ * `focus()` on the host moves focus to the inner control, so a component
21
+ * that returns focus to a slotted trigger (a menu closing on Escape) lands
22
+ * on the real button.
23
+ */
24
+ static shadowRootOptions: ShadowRootInit;
19
25
  constructor();
20
26
  /**
21
27
  * Surface theme variant (light or dark)
@@ -97,6 +103,20 @@ export declare class Button extends Base {
97
103
  */
98
104
  name: string;
99
105
  value: string;
106
+ /**
107
+ * Forwarded to the inner `<button>`, which is the element assistive
108
+ * technology sees: the button opens a popup of this type (`menu`, `listbox`,
109
+ * `dialog`…). A popup owner such as `dsgn-dropdown-menu` sets it as a
110
+ * property, so it lands on the real control rather than on the host. Ignored
111
+ * in link mode.
112
+ */
113
+ ariaHasPopup: string;
114
+ /**
115
+ * Forwarded to the inner `<button>`: whether the popup it controls is open
116
+ * (`'true'`/`'false'`, empty for none). An expanded ghost button keeps its
117
+ * hover fill, so an open menu's trigger stays marked. Ignored in link mode.
118
+ */
119
+ ariaExpanded: string;
100
120
  /**
101
121
  * Disabled state inherited from an ancestor `<fieldset>`/form owner, kept
102
122
  * separate from the authored `disabled` property. Writing the inherited state
@@ -544,6 +544,16 @@ var styles2 = css2`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.co
544
544
  opacity: 50%;
545
545
  }
546
546
  }
547
+ .aria-expanded\\:bg-button-ghost-bg-hover {
548
+ &[aria-expanded="true"] {
549
+ background-color: var(--color-button-ghost-bg-hover);
550
+ }
551
+ }
552
+ .aria-expanded\\:text-button-ghost-text-hover {
553
+ &[aria-expanded="true"] {
554
+ color: var(--color-button-ghost-text-hover);
555
+ }
556
+ }
547
557
  .motion-safe\\:animate-spin {
548
558
  @media (prefers-reduced-motion: no-preference) {
549
559
  animation: var(--animate-spin);
@@ -821,6 +831,8 @@ var appearanceClasses = {
821
831
  "text-button-ghost-text",
822
832
  "hover:text-button-ghost-text-hover",
823
833
  "hover:bg-button-ghost-bg-hover",
834
+ "aria-expanded:text-button-ghost-text-hover",
835
+ "aria-expanded:bg-button-ghost-bg-hover",
824
836
  "border-none"
825
837
  ],
826
838
  "mono-solid": [
@@ -890,9 +902,15 @@ var classNames = ({
890
902
  };
891
903
 
892
904
  // components/Button/src/Button.ts
905
+ var optional = (value) => value || nothing;
906
+
893
907
  class Button extends Base {
894
908
  static styles = [Base.styles, Button_default];
895
909
  static formAssociated = true;
910
+ static shadowRootOptions = {
911
+ ...Base.shadowRootOptions,
912
+ delegatesFocus: true
913
+ };
896
914
  #internals;
897
915
  constructor() {
898
916
  super();
@@ -910,6 +928,8 @@ class Button extends Base {
910
928
  this.loading = false;
911
929
  this.name = "";
912
930
  this.value = "";
931
+ this.ariaHasPopup = "";
932
+ this.ariaExpanded = "";
913
933
  this._formDisabled = false;
914
934
  this.#internals = this.attachInternals();
915
935
  }
@@ -1040,6 +1060,8 @@ class Button extends Base {
1040
1060
  aria-disabled=${this.loading ? "true" : nothing}
1041
1061
  aria-busy=${ariaBusy}
1042
1062
  aria-label=${ariaLabel}
1063
+ aria-haspopup=${optional(this.ariaHasPopup)}
1064
+ aria-expanded=${optional(this.ariaExpanded)}
1043
1065
  @click=${this._handleClick}
1044
1066
  >
1045
1067
  ${content}
@@ -1092,6 +1114,12 @@ __legacyDecorateClassTS([
1092
1114
  __legacyDecorateClassTS([
1093
1115
  property({ type: String, reflect: true })
1094
1116
  ], Button.prototype, "value", undefined);
1117
+ __legacyDecorateClassTS([
1118
+ property({ type: String, attribute: "aria-haspopup" })
1119
+ ], Button.prototype, "ariaHasPopup", undefined);
1120
+ __legacyDecorateClassTS([
1121
+ property({ type: String, attribute: "aria-expanded" })
1122
+ ], Button.prototype, "ariaExpanded", undefined);
1095
1123
  __legacyDecorateClassTS([
1096
1124
  state()
1097
1125
  ], Button.prototype, "_formDisabled", undefined);