@rio-m3e/core 0.1.0 → 0.3.0

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.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,44 @@
1
+ /**
2
+ * The attributes of one part of a component's markup, in HTML form: names as HTML spells them
3
+ * (`class`, `aria-pressed`, `data-size`) and string values, with `undefined` leaving an attribute
4
+ * out. Each component's `parts` returns one of these per part. See "Core output" in
5
+ * `docs/conventions.md`.
6
+ */
7
+ export type PartAttrs = Record<string, string | undefined>;
8
+ /**
9
+ * An event a component's handler (`handleClick`) takes: a DOM event, or a framework's own, such
10
+ * as React's synthetic events. Core only cancels it, and hands the same event back to the adapter
11
+ * (`onClick`), so components are generic over its type, with `MouseEvent` as the default.
12
+ */
13
+ export interface CancelableEvent {
14
+ preventDefault(): void;
15
+ }
16
+ /**
17
+ * One node of a component's skeleton: the markup an adapter renders, part by part. Adapters
18
+ * render it their own way; contract tests check what they rendered against it. Each component
19
+ * exports its skeleton (`buttonSkeleton`, `switchSkeleton`, ...).
20
+ *
21
+ * `Part` narrows the names to the component's parts, so a typo fails the build. `When` lists the
22
+ * decisions the skeleton may use; leave it out when every part always renders. Skeletons are
23
+ * shared objects, so everything is read-only.
24
+ */
25
+ export interface PartNode<Part extends string = string, When extends string = never> {
26
+ /** The part, named as in the component's `parts`. */
27
+ readonly part: Part;
28
+ /** The element the part is, as `tagName` in lower case. */
29
+ readonly tag: keyof HTMLElementTagNameMap;
30
+ /**
31
+ * The component API's decision that says whether the part renders, such as `'showsIcon'`.
32
+ * Without it, the part always renders.
33
+ */
34
+ readonly when?: When;
35
+ /**
36
+ * Content from the app (slots, children) goes inside. The content itself isn't checked against
37
+ * the skeleton; where a component picks between contents, its contract test checks the pick.
38
+ */
39
+ readonly content?: true;
40
+ readonly children?: readonly PartNode<Part, When>[];
41
+ }
1
42
  export declare const BUTTON_VARIANTS: readonly ["filled", "tonal", "outlined", "elevated", "text"];
2
43
  export type ButtonVariant = (typeof BUTTON_VARIANTS)[number];
3
44
  /** M3E button sizes. `small` is the default. */
@@ -28,6 +69,11 @@ export interface ButtonProps {
28
69
  softDisabled?: boolean;
29
70
  /** Defaults to `'button'`, so a button inside a form doesn't submit it by accident. */
30
71
  type?: 'button' | 'submit' | 'reset';
72
+ /**
73
+ * Identifies the button inside a button group that keeps a selection. Such a group makes the
74
+ * button a toggle and decides whether it is selected.
75
+ */
76
+ value?: ButtonGroupValue;
31
77
  }
32
78
  export declare const buttonDefaults: {
33
79
  variant: "filled";
@@ -39,20 +85,202 @@ export declare const buttonDefaults: {
39
85
  softDisabled: false;
40
86
  type: "button";
41
87
  };
42
- export interface ButtonOptions {
88
+ /** What the adapter has to put in the button, which only the adapter can see. */
89
+ export interface ButtonContent {
90
+ /** Whether there is a leading icon. */
91
+ icon: boolean;
92
+ }
93
+ export interface ButtonOptions<E extends CancelableEvent = MouseEvent> {
43
94
  /** Returns the current prop values. The adapter owns the state. */
44
95
  props: () => ButtonProps;
45
- /** Called for a click that isn't blocked. */
46
- onClick: (event: MouseEvent) => void;
47
- /** Called after an unblocked click on a toggle button, with the new value. */
48
- onSelectedChange: (selected: boolean) => void;
96
+ /** Returns what content the adapter has. Without it, the button has no icon. */
97
+ content?: () => ButtonContent;
98
+ /** Called for a click that isn't blocked, with the event `handleClick` got. */
99
+ onClick: (event: E) => void;
100
+ /** Called after an unblocked click on a toggle button, with the requested value. */
101
+ onChange: (selected: boolean) => void;
102
+ }
103
+ /**
104
+ * The button's markup, part by part. `buttonSkeleton` says how the adapter nests them.
105
+ */
106
+ export interface ButtonParts {
107
+ root: () => PartAttrs;
108
+ /** Extends small buttons to the 48px target. */
109
+ touch: () => PartAttrs;
110
+ icon: () => PartAttrs;
111
+ label: () => PartAttrs;
49
112
  }
50
- export interface ButtonApi {
51
- rootAttrs: () => Record<string, string | undefined>;
113
+ /** How the adapter renders the button's parts. The icon holds the leading icon. */
114
+ export declare const buttonSkeleton: PartNode<keyof ButtonParts, 'showsIcon'>;
115
+ export interface ButtonApi<E extends CancelableEvent = MouseEvent> {
116
+ parts: ButtonParts;
117
+ /** Whether to render the icon part. */
118
+ showsIcon: () => boolean;
52
119
  /** Attach to the root element's `click`. */
53
- handleClick: (event: MouseEvent) => void;
120
+ handleClick: (event: E) => void;
121
+ }
122
+ export declare function createButton<E extends CancelableEvent = MouseEvent>(options: ButtonOptions<E>): ButtonApi<E>;
123
+ /**
124
+ * M3E button group variants. A standard group spaces its buttons and lets a pressed button push
125
+ * its neighbors; a connected group joins them with 2px gaps and shared corners, and fills the
126
+ * width it is given.
127
+ */
128
+ export declare const BUTTON_GROUP_VARIANTS: readonly ["standard", "connected"];
129
+ export type ButtonGroupVariant = (typeof BUTTON_GROUP_VARIANTS)[number];
130
+ /** Whether the group's buttons can be selected: none, one at a time, or any number. */
131
+ export declare const BUTTON_GROUP_SELECTIONS: readonly ["none", "single", "multiple"];
132
+ export type ButtonGroupSelection = (typeof BUTTON_GROUP_SELECTIONS)[number];
133
+ /** Identifies a button inside a selecting group, through its `value` prop. */
134
+ export type ButtonGroupValue = string | number;
135
+ /** The selected value for `single` (or `undefined`), or the selected values for `multiple`. */
136
+ export type ButtonGroupSelected = ButtonGroupValue | ButtonGroupValue[] | undefined;
137
+ export interface ButtonGroupProps {
138
+ /** Defaults to `'standard'`. */
139
+ variant?: ButtonGroupVariant;
140
+ /** The size of every button in the group that doesn't set its own. Defaults to `'small'`. */
141
+ size?: ButtonSize;
142
+ /** The shape of every button in the group that doesn't set its own. Defaults to `'round'`. */
143
+ shape?: ButtonShape;
144
+ /**
145
+ * Turns buttons that have a `value` into toggle buttons whose selection the group keeps.
146
+ * Defaults to `'none'`, where every button acts on its own.
147
+ */
148
+ selection?: ButtonGroupSelection;
149
+ /** With a selection, keeps at least one button selected: the last one can't be turned off. */
150
+ required?: boolean;
151
+ /** The selected value (`single`) or values (`multiple`). */
152
+ selected?: ButtonGroupSelected;
153
+ }
154
+ export declare const buttonGroupDefaults: {
155
+ variant: "standard";
156
+ size: "small";
157
+ shape: "round";
158
+ selection: "none";
159
+ required: false;
160
+ };
161
+ export interface ButtonGroupOptions {
162
+ /** Returns the current prop values. The adapter owns the state. */
163
+ props: () => ButtonGroupProps;
164
+ /** Called with the requested selection. Adapters wire it to v-model, a callback prop, and so on. */
165
+ onChange: (selected: ButtonGroupSelected) => void;
166
+ }
167
+ /**
168
+ * The button group's markup. `buttonGroupSkeleton` says how the adapter renders it.
169
+ */
170
+ export interface ButtonGroupParts {
171
+ root: () => PartAttrs;
172
+ }
173
+ /** How the adapter renders the button group: one root, with the buttons as its direct children. */
174
+ export declare const buttonGroupSkeleton: PartNode<keyof ButtonGroupParts>;
175
+ export interface ButtonGroupApi {
176
+ parts: ButtonGroupParts;
177
+ /** The size for buttons that don't set their own, with the default filled in. */
178
+ size: () => ButtonSize;
179
+ /** The shape for buttons that don't set their own, with the default filled in. */
180
+ shape: () => ButtonShape;
181
+ /** The selection mode, with the default filled in. */
182
+ selection: () => ButtonGroupSelection;
183
+ /** Whether the button with this value is selected. */
184
+ isSelected: (value: ButtonGroupValue) => boolean;
185
+ /**
186
+ * Requests turning the button with this value on or off. The group applies its selection rules:
187
+ * a single selection turns the previous button off, and `required` keeps the last one on.
188
+ */
189
+ select: (value: ButtonGroupValue, selected: boolean) => void;
190
+ }
191
+ export declare function createButtonGroup(options: ButtonGroupOptions): ButtonGroupApi;
192
+ export interface ButtonGroupInteractionOptions {
193
+ /**
194
+ * How much wider a pressed button gets, as a share of its width. Each neighbor gives up at most
195
+ * the same share of its own width. Defaults to 0.15, the spec's 15%.
196
+ */
197
+ pressedWidthRatio?: number;
198
+ }
199
+ export interface ButtonGroupHandle {
200
+ detach(): void;
201
+ }
202
+ export declare function attachButtonGroup(root: HTMLElement, options?: ButtonGroupInteractionOptions): ButtonGroupHandle;
203
+ /**
204
+ * The dialog's ARIA role: `alertdialog` for a prompt that asks for a response, such as a
205
+ * confirmation, and `dialog` for one that holds a list or a form.
206
+ */
207
+ export declare const DIALOG_ROLES: readonly ["alertdialog", "dialog"];
208
+ export type DialogRole = (typeof DIALOG_ROLES)[number];
209
+ export interface DialogProps {
210
+ /** Whether the dialog is shown. */
211
+ open?: boolean;
212
+ /** Defaults to `'alertdialog'`. */
213
+ role?: DialogRole;
214
+ }
215
+ export declare const dialogDefaults: {
216
+ open: false;
217
+ role: "alertdialog";
218
+ };
219
+ /** What the adapter has to put in the dialog, which only the adapter can see. */
220
+ export interface DialogContent {
221
+ /** An icon above the headline */
222
+ icon: boolean;
223
+ headline: boolean;
224
+ /** Buttons below the content */
225
+ actions: boolean;
226
+ }
227
+ export interface DialogOptions {
228
+ props: () => DialogProps;
229
+ /** Without it, the dialog has only its content. */
230
+ content?: () => DialogContent;
231
+ /** Ids for the parts the dialog refers to, unique in the document. The adapter makes them. */
232
+ ids: {
233
+ headline: string;
234
+ content: string;
235
+ };
54
236
  }
55
- export declare function createButton(options: ButtonOptions): ButtonApi;
237
+ export interface DialogParts {
238
+ /** The `<dialog>`, shown in the top layer */
239
+ root: () => PartAttrs;
240
+ /** Holds the icon, which only decorates: the headline says what the dialog is for. */
241
+ icon: () => PartAttrs;
242
+ headline: () => PartAttrs;
243
+ /** Holds the supporting text, or a list or a form. It scrolls when it doesn't fit. */
244
+ content: () => PartAttrs;
245
+ /** Holds the buttons. */
246
+ actions: () => PartAttrs;
247
+ }
248
+ type DialogPart = keyof DialogParts;
249
+ type DialogWhen = 'showsIcon' | 'showsHeadline' | 'showsActions';
250
+ /**
251
+ * The markup of an open dialog. The parts inside render from opening until `attachDialog`'s
252
+ * `onClosed`; a closed dialog is the empty `<dialog>`.
253
+ */
254
+ export declare const dialogSkeleton: PartNode<DialogPart, DialogWhen>;
255
+ export interface DialogApi extends Record<DialogWhen, () => boolean> {
256
+ parts: DialogParts;
257
+ }
258
+ export declare function createDialog(options: DialogOptions): DialogApi;
259
+ /** What asks the dialog to close: Escape (or the back gesture), or a click on the scrim */
260
+ export type DialogCancelReason = 'escape' | 'scrim';
261
+ export interface DialogCancelEvent extends CancelableEvent {
262
+ readonly reason: DialogCancelReason;
263
+ readonly defaultPrevented: boolean;
264
+ }
265
+ export interface DialogDomOptions {
266
+ /** Escape or a click on the scrim asks to close. Canceling the event keeps the dialog open. */
267
+ onCancel?: (event: DialogCancelEvent) => void;
268
+ /** The dialog closes on its own, or is asked to and isn't kept open. The adapter sets `open`. */
269
+ onOpenChange: (open: boolean) => void;
270
+ /**
271
+ * The dialog finished closing, its motion included, and hasn't opened again since. The adapter
272
+ * renders the content from opening until then.
273
+ */
274
+ onClosed?: () => void;
275
+ }
276
+ export interface DialogDomHandle {
277
+ /** Brings the dialog in line with `open`. Call it once attached, and whenever `open` changes. */
278
+ sync(open: boolean): void;
279
+ /** Call it after the content changed, to update the dividers. */
280
+ update(): void;
281
+ detach(): void;
282
+ }
283
+ export declare function attachDialog(root: HTMLDialogElement, options: DialogDomOptions): DialogDomHandle;
56
284
  export declare const ICON_BUTTON_VARIANTS: readonly ["filled", "tonal", "outlined", "standard"];
57
285
  export type IconButtonVariant = (typeof ICON_BUTTON_VARIANTS)[number];
58
286
  /** M3E icon button widths. `default` makes a square (or circle) as wide as it is tall. */
@@ -82,6 +310,8 @@ export interface IconButtonProps {
82
310
  softDisabled?: boolean;
83
311
  /** Defaults to `'button'`, so it doesn't submit a form by accident. */
84
312
  type?: 'button' | 'submit' | 'reset';
313
+ /** Identifies the button inside a button group that keeps a selection, as on the button. */
314
+ value?: ButtonGroupValue;
85
315
  }
86
316
  export declare const iconButtonDefaults: {
87
317
  variant: "filled";
@@ -94,15 +324,327 @@ export declare const iconButtonDefaults: {
94
324
  softDisabled: false;
95
325
  type: "button";
96
326
  };
97
- export interface IconButtonOptions {
327
+ /** What the adapter has to put in the icon button, which only the adapter can see. */
328
+ export interface IconButtonContent {
329
+ /** Whether there is an icon for the selected state. */
330
+ selectedIcon: boolean;
331
+ }
332
+ export interface IconButtonOptions<E extends CancelableEvent = MouseEvent> {
98
333
  /** Returns the current prop values. The adapter owns the state. */
99
334
  props: () => IconButtonProps;
100
- /** Called for a click that isn't blocked. */
101
- onClick: (event: MouseEvent) => void;
102
- /** Called after an unblocked click on a toggle button, with the new value. */
103
- onSelectedChange: (selected: boolean) => void;
335
+ /** Returns what content the adapter has. Without it, there is no selected icon. */
336
+ content?: () => IconButtonContent;
337
+ /** Called for a click that isn't blocked, with the event `handleClick` got. */
338
+ onClick: (event: E) => void;
339
+ /** Called after an unblocked click on a toggle button, with the requested value. */
340
+ onChange: (selected: boolean) => void;
341
+ }
342
+ /**
343
+ * The icon button's markup, part by part. `iconButtonSkeleton` says how the adapter nests them.
344
+ */
345
+ export interface IconButtonParts {
346
+ root: () => PartAttrs;
347
+ /** Extends small icon buttons to the 48px target. */
348
+ touch: () => PartAttrs;
349
+ icon: () => PartAttrs;
350
+ }
351
+ /**
352
+ * How the adapter renders the icon button's parts. The icon holds the selected icon when
353
+ * `showsSelectedIcon()`, otherwise the default one.
354
+ */
355
+ export declare const iconButtonSkeleton: PartNode<keyof IconButtonParts>;
356
+ export interface IconButtonApi<E extends CancelableEvent = MouseEvent> {
357
+ parts: IconButtonParts;
358
+ /** Whether the icon part shows the selected icon instead of the default one. */
359
+ showsSelectedIcon: () => boolean;
360
+ /** Attach to the root element's `click`. */
361
+ handleClick: (event: E) => void;
362
+ }
363
+ export declare function createIconButton<E extends CancelableEvent = MouseEvent>(options: IconButtonOptions<E>): IconButtonApi<E>;
364
+ export type AnchoredSide = 'top' | 'bottom' | 'start' | 'end';
365
+ export type AnchoredAlign = 'start' | 'center' | 'end';
366
+ /** A point in viewport coordinates, such as a pointer event's `clientX` and `clientY`. */
367
+ export interface AnchorPoint {
368
+ x: number;
369
+ y: number;
370
+ }
371
+ export interface AnchoredPositionOptions {
372
+ /** Returns the anchor. Placing is skipped while it returns nothing. */
373
+ anchor: () => Element | AnchorPoint | null | undefined;
374
+ /** The preferred side of the anchor. Defaults to `'bottom'`. */
375
+ side?: AnchoredSide;
376
+ /** Which edges line up along that side. Defaults to `'start'`. */
377
+ align?: AnchoredAlign;
378
+ /** Space between the anchor and the element, in px. Defaults to 0. */
379
+ offset?: number;
380
+ /**
381
+ * Moves the element along the anchor, in px, once lined up: down beside the anchor, toward the
382
+ * end of the line above or below it. Defaults to 0. A submenu uses it to line its first item up
383
+ * with the item that opened it.
384
+ */
385
+ alignOffset?: number;
386
+ /** Sides to try, in order, when the preferred one has no room. Defaults to the opposite side. */
387
+ fallbacks?: readonly AnchoredSide[];
388
+ /** Space kept from the edges of the window, in px. Defaults to 8. */
389
+ padding?: number;
390
+ }
391
+ export interface AnchoredPositionHandle {
392
+ /** Places the element now, for example after the anchor point moved. */
393
+ update(): void;
394
+ detach(): void;
395
+ }
396
+ export declare function attachAnchoredPosition(floating: HTMLElement, options: AnchoredPositionOptions): AnchoredPositionHandle;
397
+ /** M3E vertical menu color styles: surface-based, or tertiary-based for more emphasis. */
398
+ export declare const MENU_VARIANTS: readonly ["standard", "vibrant"];
399
+ export type MenuVariant = (typeof MENU_VARIANTS)[number];
400
+ /** Whether the menu's items can be selected: none, one at a time, or any number. */
401
+ export declare const MENU_SELECTIONS: readonly ["none", "single", "multiple"];
402
+ export type MenuSelection = (typeof MENU_SELECTIONS)[number];
403
+ /** Identifies an item in a selecting menu, through its `value` prop. */
404
+ export type MenuValue = string | number;
405
+ /** The selected value for `single` (or `undefined`), or the selected values for `multiple`. */
406
+ export type MenuSelected = MenuValue | MenuValue[] | undefined;
407
+ export interface MenuProps {
408
+ /** Whether the menu is shown. */
409
+ open?: boolean;
410
+ /** Defaults to `'standard'`. */
411
+ variant?: MenuVariant;
412
+ /**
413
+ * Makes items with a `value` selectable, and keeps the selection. Defaults to `'none'`, where
414
+ * choosing an item only runs its action.
415
+ */
416
+ selection?: MenuSelection;
417
+ /** The selected value (`single`) or values (`multiple`). */
418
+ selected?: MenuSelected;
419
+ /** Names the menu for assistive technology, such as the label of the button that opens it. */
420
+ label?: string;
421
+ /** The side of the anchor it opens on, when there is room. Defaults to `'bottom'`. */
422
+ side?: AnchoredSide;
423
+ /** Which edges line up with the anchor's. Defaults to `'start'`. */
424
+ align?: AnchoredAlign;
425
+ /** Space between the anchor and the menu, in px. Defaults to 0; a split button's menu uses 4. */
426
+ offset?: number;
427
+ /** Opens and closes without the expanding motion, as dense desktop products may. */
428
+ instant?: boolean;
429
+ }
430
+ export declare const menuDefaults: {
431
+ open: false;
432
+ variant: "standard";
433
+ selection: "none";
434
+ side: "bottom";
435
+ align: "start";
436
+ offset: number;
437
+ instant: false;
438
+ };
439
+ export interface MenuOptions {
440
+ /** Returns the current prop values. The adapter owns the state. */
441
+ props: () => MenuProps;
442
+ /** Called with the requested selection. */
443
+ onChange: (selected: MenuSelected) => void;
444
+ /** Called with the requested `open`, such as `false` once an item is chosen. */
445
+ onOpenChange: (open: boolean) => void;
446
+ }
447
+ export interface MenuParts {
448
+ /** The surface, shown in the top layer. Its children are items, dividers, and groups. */
449
+ root: () => PartAttrs;
450
+ }
451
+ export declare const menuSkeleton: PartNode<keyof MenuParts>;
452
+ export interface MenuApi {
453
+ parts: MenuParts;
454
+ /** The selection mode, with the default filled in. */
455
+ selection: () => MenuSelection;
456
+ /** Whether the item with this value is selected. */
457
+ isSelected: (value: MenuValue) => boolean;
458
+ /**
459
+ * An item was chosen. With a `value` in a selecting menu, the selection rules apply: `single`
460
+ * selects it, `multiple` toggles it. Then the menu asks to close, except for `multiple`, which
461
+ * stays open until dismissed.
462
+ */
463
+ choose: (value?: MenuValue) => void;
464
+ /** Asks the menu to close. In a submenu, the whole menu closes, as after a choice. */
465
+ close: () => void;
466
+ /** The color style, with the default filled in, which submenus take on. */
467
+ variant: () => MenuVariant;
468
+ /** Whether the menu opens without its motion, which submenus take on. */
469
+ instant: () => boolean;
470
+ }
471
+ export declare function createMenu(options: MenuOptions): MenuApi;
472
+ export interface MenuItemProps {
473
+ /** Makes the item selectable in a menu that keeps a selection. */
474
+ value?: MenuValue;
475
+ /** Still focusable, but can't be chosen. */
476
+ disabled?: boolean;
477
+ }
478
+ export declare const menuItemDefaults: {
479
+ disabled: false;
480
+ };
481
+ /** What the adapter has to put in the item, which only the adapter can see. */
482
+ export interface MenuItemContent {
483
+ /** A leading icon */
484
+ icon: boolean;
485
+ /** Supporting text under the label */
486
+ supportingText: boolean;
487
+ /** Trailing text, such as a keyboard shortcut */
488
+ trailingText: boolean;
489
+ /** A trailing icon */
490
+ trailingIcon: boolean;
491
+ }
492
+ export interface MenuItemOptions<E extends CancelableEvent = MouseEvent> {
493
+ props: () => MenuItemProps;
494
+ /** Without it, the item has only a label. */
495
+ content?: () => MenuItemContent;
496
+ /** The menu around the item. Without one, the item acts as in a menu without a selection. */
497
+ menu?: () => Pick<MenuApi, 'selection' | 'isSelected' | 'choose'> | undefined;
498
+ /**
499
+ * Called for a click on the item that isn't blocked, before the menu is told, with the event
500
+ * `handleClick` got.
501
+ */
502
+ onClick: (event: E) => void;
503
+ /**
504
+ * Makes it the item that opens a submenu (see `createSubMenu`): a click opens the submenu rather
505
+ * than choosing the item.
506
+ */
507
+ submenu?: {
508
+ /** Whether the submenu is open */
509
+ open: () => boolean;
510
+ /** Asks for the submenu to open. */
511
+ onOpen: () => void;
512
+ };
513
+ }
514
+ export interface MenuItemParts {
515
+ /** A `<button>` with the item's role */
516
+ root: () => PartAttrs;
517
+ /** Extends the 44px item to a 48px target, over the space between items. */
518
+ touch: () => PartAttrs;
519
+ icon: () => PartAttrs;
520
+ /** Holds the label and the supporting text, one above the other */
521
+ text: () => PartAttrs;
522
+ label: () => PartAttrs;
523
+ supportingText: () => PartAttrs;
524
+ trailingText: () => PartAttrs;
525
+ trailingIcon: () => PartAttrs;
526
+ }
527
+ type MenuItemWhen = 'showsIcon' | 'showsSupportingText' | 'showsTrailingText' | 'showsTrailingIcon';
528
+ export declare const menuItemSkeleton: PartNode<keyof MenuItemParts, MenuItemWhen>;
529
+ export interface MenuItemApi<E extends CancelableEvent = MouseEvent> extends Record<MenuItemWhen, () => boolean> {
530
+ parts: MenuItemParts;
531
+ /** Whether the item is selected, in a menu that keeps a selection. */
532
+ selected: () => boolean;
533
+ /** Attach to the item's `click`. */
534
+ handleClick: (event: E) => void;
104
535
  }
105
- export declare function createIconButton(options: IconButtonOptions): ButtonApi;
536
+ export declare function createMenuItem<E extends CancelableEvent = MouseEvent>(options: MenuItemOptions<E>): MenuItemApi<E>;
537
+ export interface SubMenuProps {
538
+ /** The label of the item that opens the submenu, which also names the submenu. */
539
+ label?: string;
540
+ /** The item stays focusable but doesn't open the submenu. */
541
+ disabled?: boolean;
542
+ /** As a menu's: a submenu keeps its own selection, such as a sort order. */
543
+ selection?: MenuSelection;
544
+ /** The selected value (`single`) or values (`multiple`). */
545
+ selected?: MenuSelected;
546
+ }
547
+ export interface SubMenuOptions {
548
+ props: () => SubMenuProps;
549
+ /** What the adapter put in the item that opens it. Without it, the item has only a label. */
550
+ content?: () => MenuItemContent;
551
+ /** The menu the submenu is in. Its color style and motion carry over, and choosing closes it. */
552
+ menu?: () => Pick<MenuApi, 'variant' | 'instant' | 'close'> | undefined;
553
+ /** Whether the submenu is open: the adapter owns it. */
554
+ open: () => boolean;
555
+ /** Called with the requested selection. */
556
+ onChange: (selected: MenuSelected) => void;
557
+ /** Called with the requested `open`: `true` for a click on its item. */
558
+ onOpenChange: (open: boolean) => void;
559
+ }
560
+ export interface SubMenuApi {
561
+ /**
562
+ * The item that opens the submenu, with the parts of a menu item (`menuItemSkeleton`). A click on
563
+ * it reports no event, so its `handleClick` takes any event it can cancel.
564
+ */
565
+ item: MenuItemApi<CancelableEvent>;
566
+ /** The submenu, with the parts of a menu (`menuSkeleton`). Its items take it as their menu. */
567
+ menu: MenuApi;
568
+ }
569
+ /**
570
+ * A submenu and the item that opens it. The two render side by side in the menu, the item where it
571
+ * sits and the submenu after it; the submenu shows in the top layer next to the item.
572
+ */
573
+ export declare function createSubMenu(options: SubMenuOptions): SubMenuApi;
574
+ export interface MenuGroupProps {
575
+ /** A label shown above the group's items, which also names the group. */
576
+ label?: string;
577
+ }
578
+ export interface MenuGroupParts {
579
+ root: () => PartAttrs;
580
+ /** The visible label. Assistive technology reads the name from the root instead. */
581
+ label: () => PartAttrs;
582
+ /** Holds the group's items. */
583
+ items: () => PartAttrs;
584
+ }
585
+ export declare const menuGroupSkeleton: PartNode<keyof MenuGroupParts, 'showsLabel'>;
586
+ export interface MenuGroupApi {
587
+ parts: MenuGroupParts;
588
+ showsLabel: () => boolean;
589
+ }
590
+ export declare function createMenuGroup(options: {
591
+ props: () => MenuGroupProps;
592
+ }): MenuGroupApi;
593
+ export interface MenuDividerParts {
594
+ root: () => PartAttrs;
595
+ }
596
+ export declare const menuDividerSkeleton: PartNode<keyof MenuDividerParts>;
597
+ export declare function createMenuDivider(): {
598
+ parts: MenuDividerParts;
599
+ };
600
+ /**
601
+ * A point a menu opens at, such as where a context menu was asked for, in viewport coordinates.
602
+ * With `element`, what the point is on, the menu keeps to that spot on it as it scrolls, as it
603
+ * follows an element anchor. Without, it stays where the point is on the screen.
604
+ */
605
+ export interface MenuAnchorPoint extends AnchorPoint {
606
+ element?: Element;
607
+ }
608
+ export interface MenuDomOptions {
609
+ /** What the menu opens from: an element, such as a button, or a point, for a context menu. */
610
+ anchor: () => HTMLElement | MenuAnchorPoint | null | undefined;
611
+ /** The placement props, read when the menu opens. */
612
+ props: () => Pick<MenuProps, 'side' | 'align' | 'offset'>;
613
+ /**
614
+ * The menu closed, or asks to close, on its own: Escape, a click outside, Tab, or another
615
+ * overlay opening. The adapter sets `open` to false.
616
+ */
617
+ onClose: () => void;
618
+ }
619
+ export interface MenuDomHandle {
620
+ /** Brings the menu in line with `open`. Call it once attached, and whenever `open` changes. */
621
+ sync(open: boolean): void;
622
+ /**
623
+ * Call it when the anchor changes. An anchor other than the one the menu opened at is a new
624
+ * request, as from another right-click: an open menu opens afresh there, with its opening motion.
625
+ * One that fills in where there was none, such as a template ref, only places it.
626
+ */
627
+ update(): void;
628
+ detach(): void;
629
+ }
630
+ export interface SubMenuDomOptions {
631
+ /**
632
+ * The submenu opens or closes on its own: the pointer, the arrow keys, Escape, or a click
633
+ * elsewhere. The adapter sets its `open` to match.
634
+ */
635
+ onOpenChange: (open: boolean) => void;
636
+ }
637
+ export interface SubMenuDomHandle {
638
+ /** Brings the submenu in line with `open`. Call it once attached, and whenever `open` changes. */
639
+ sync(open: boolean): void;
640
+ detach(): void;
641
+ }
642
+ export declare function attachMenu(root: HTMLElement, options: MenuDomOptions): MenuDomHandle;
643
+ /**
644
+ * A submenu's behavior in the DOM: a menu (see `attachMenu`) that `item` opens, shown beside it.
645
+ * `item` sits in a menu that `attachMenu` or this function runs, and `root` follows it.
646
+ */
647
+ export declare function attachSubMenu(item: HTMLElement, root: HTMLElement, options: SubMenuDomOptions): SubMenuDomHandle;
106
648
  export interface DragInfo {
107
649
  /** Distance from where the press started, in px. */
108
650
  dx: number;
@@ -126,6 +668,41 @@ export interface DragHandle {
126
668
  detach(): void;
127
669
  }
128
670
  export declare function attachDrag(element: HTMLElement, options: DragOptions): DragHandle;
671
+ export type OverlayMode = 'auto' | 'manual' | 'modal';
672
+ /**
673
+ * Why an overlay closed, or asks to close, without the adapter asking:
674
+ * - `'escape'`: Escape, or the back gesture on a dialog. The overlay is still open, and the adapter
675
+ * can turn the request down, as a dialog with unsaved changes might.
676
+ * - `'dismiss'`: the browser closed it: a click outside, or another popover or a modal dialog
677
+ * opening.
678
+ * - `'parent'`: the overlay it was opened from closed.
679
+ * - `'replaced'`: another overlay opened, and this transient one gave way.
680
+ *
681
+ * All but `'escape'` arrive after the overlay closed, so the adapter has to follow them.
682
+ */
683
+ export type OverlayCloseReason = 'escape' | 'dismiss' | 'parent' | 'replaced';
684
+ export interface OverlayOptions {
685
+ mode: OverlayMode;
686
+ /** Closes when any other overlay opens, as a tooltip does. */
687
+ transient?: boolean;
688
+ /** Keeps clicks and presses in the element from bubbling past it, as a tooltip and a dialog do. */
689
+ contain?: boolean;
690
+ /** The overlay closed, or asks to close, without the adapter asking. */
691
+ onClose: (reason: OverlayCloseReason) => void;
692
+ }
693
+ export interface OverlayHandle {
694
+ /**
695
+ * Shows the element. `source` is what opened it, such as a menu item: a popover shown from
696
+ * inside an auto popover keeps that one open, and closes with it.
697
+ */
698
+ show(source?: HTMLElement): void;
699
+ /** Closes the element, without calling `onClose`. */
700
+ hide(): void;
701
+ /** Whether an overlay that opened after this one is still open, other than a transient one. */
702
+ covered(): boolean;
703
+ detach(): void;
704
+ }
705
+ export declare function attachOverlay(element: HTMLElement, options: OverlayOptions): OverlayHandle;
129
706
  export interface RippleHandle {
130
707
  /** Removes the listeners and the `data-ripple` marker. */
131
708
  detach(): void;
@@ -147,22 +724,21 @@ export declare function attachRipple(element: HTMLElement, options?: RippleOptio
147
724
  export interface ShapeMorphOptions {
148
725
  /** The motion token whose spring drives the morph. Defaults to `'fast-spatial'`. */
149
726
  token?: string;
150
- /** Attributes whose changes can move the target. Defaults to Rio's state attributes. */
151
- watch?: string[];
152
727
  }
153
728
  export interface ShapeMorphHandle {
154
729
  detach(): void;
155
730
  }
156
731
  export declare function attachShapeMorph(element: HTMLElement, options?: ShapeMorphOptions): ShapeMorphHandle;
157
732
  export interface SpringPropertyOptions {
158
- /** A registered <length> custom property that holds the target for the current state. */
733
+ /**
734
+ * A registered custom property that holds the target for the current state: a <length>, read
735
+ * in px, or an <angle>, read in deg. The stylesheet has to transition it (see above).
736
+ */
159
737
  target: string;
160
- /** Writes the animated value in px, or clears it with `undefined` at rest. */
738
+ /** Writes the animated value (px or deg), or clears it with `undefined` at rest. */
161
739
  apply: (value: number | undefined) => void;
162
740
  /** The motion token whose spring drives the value. Defaults to `'fast-spatial'`. */
163
741
  token?: string;
164
- /** Attributes whose changes can move the target. Defaults to Rio's state attributes. */
165
- watch?: string[];
166
742
  }
167
743
  export interface SpringPropertyHandle {
168
744
  /** The current animated value. */
@@ -173,8 +749,6 @@ export interface SpringPropertyHandle {
173
749
  release(velocity?: number): void;
174
750
  detach(): void;
175
751
  }
176
- /** The data attributes Rio's components use for state. */
177
- export declare const STATE_ATTRIBUTES: string[];
178
752
  export declare function attachSpringProperty(element: HTMLElement, options: SpringPropertyOptions): SpringPropertyHandle;
179
753
  export interface SpringParams {
180
754
  /** 1 settles without overshoot; lower values bounce, higher values approach more slowly. */
@@ -220,6 +794,11 @@ export interface Spring {
220
794
  }
221
795
  /** A spring that animates a single number with `requestAnimationFrame`. */
222
796
  export declare function createSpring(options: SpringOptions): Spring;
797
+ /**
798
+ * Whether the user asked for reduced motion. Spatial springs should jump straight to their target
799
+ * (or be skipped) when it is on; see `docs/foundations.md`, section 3.
800
+ */
801
+ export declare function prefersReducedMotion(): boolean;
223
802
  /**
224
803
  * Reads a motion token's spring from CSS variables on an element, for example
225
804
  * `--rio-motion-fast-spatial-damping` and `-stiffness`. Reading them from the element, rather than
@@ -227,11 +806,107 @@ export declare function createSpring(options: SpringOptions): Spring;
227
806
  * Returns `undefined` when the theme doesn't define them.
228
807
  */
229
808
  export declare function readSpringParams(element: Element, token: string): SpringParams | undefined;
809
+ export interface SplitButtonMenuIconHandle {
810
+ detach(): void;
811
+ }
812
+ /** Attach to the trailing button. */
813
+ export declare function attachSplitButtonMenuIcon(trailing: HTMLElement): SplitButtonMenuIconHandle;
814
+ /** Split button colors: the button's, without text. */
815
+ export declare const SPLIT_BUTTON_VARIANTS: readonly ["filled", "tonal", "outlined", "elevated"];
816
+ export type SplitButtonVariant = (typeof SPLIT_BUTTON_VARIANTS)[number];
817
+ /** What the trailing button opens, for `aria-haspopup`. */
818
+ export type SplitButtonPopup = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';
819
+ export interface SplitButtonProps {
820
+ /**
821
+ * Names the trailing button. It should say that more choices for the leading action are there,
822
+ * such as "More watch options" next to "Watch later".
823
+ */
824
+ menuLabel: string;
825
+ /**
826
+ * Names the leading button when it has no label text, only an icon. Ignored when there is label
827
+ * text, so the visible label stays the name (WCAG 2.5.3).
828
+ */
829
+ label?: string;
830
+ /** Defaults to `'filled'`. */
831
+ variant?: SplitButtonVariant;
832
+ /** Defaults to `'small'`. */
833
+ size?: ButtonSize;
834
+ /** Whether what the trailing button opens is open. */
835
+ open?: boolean;
836
+ /** What the trailing button opens. Defaults to `'menu'`. */
837
+ popup?: SplitButtonPopup;
838
+ /** Native disabled, on both buttons: not focusable and not pressable. The spec's behavior. */
839
+ disabled?: boolean;
840
+ /** Disabled but still focusable, on both buttons, as on the button. Deviates from the spec. */
841
+ softDisabled?: boolean;
842
+ /** The leading button's type. Defaults to `'button'`; the trailing button is always `'button'`. */
843
+ type?: 'button' | 'submit' | 'reset';
844
+ }
845
+ export declare const splitButtonDefaults: {
846
+ variant: "filled";
847
+ size: "small";
848
+ open: false;
849
+ popup: "menu";
850
+ disabled: false;
851
+ softDisabled: false;
852
+ type: "button";
853
+ };
854
+ /** What the adapter has to put in the leading button, which only the adapter can see. */
855
+ export interface SplitButtonContent {
856
+ /** Whether there is a leading icon. */
857
+ icon: boolean;
858
+ /** Whether there is label text. */
859
+ label: boolean;
860
+ }
861
+ export interface SplitButtonOptions<E extends CancelableEvent = MouseEvent> {
862
+ /** Returns the current prop values. The adapter owns the state. */
863
+ props: () => SplitButtonProps;
864
+ /** Returns what content the adapter has. Without it, the leading button has only a label. */
865
+ content?: () => SplitButtonContent;
866
+ /**
867
+ * Called for a click on the leading button that isn't blocked, with the event
868
+ * `handleLeadingClick` got.
869
+ */
870
+ onClick: (event: E) => void;
871
+ /** Called with the requested `open` when the trailing button is clicked and isn't blocked. */
872
+ onChange: (open: boolean) => void;
873
+ }
874
+ /** The split button's markup, part by part. `splitButtonSkeleton` says how the adapter nests them. */
875
+ export interface SplitButtonParts {
876
+ root: () => PartAttrs;
877
+ /** The main action, a button with an icon, label text, or both. */
878
+ leading: () => PartAttrs;
879
+ /** The button that opens the menu. */
880
+ trailing: () => PartAttrs;
881
+ /** Extends each button to the 48px target. Both buttons have one. */
882
+ touch: () => PartAttrs;
883
+ icon: () => PartAttrs;
884
+ label: () => PartAttrs;
885
+ /** Holds the expand icon, which turns when the menu opens. */
886
+ menuIcon: () => PartAttrs;
887
+ }
888
+ /**
889
+ * How the adapter renders the split button's parts. The icon and the label hold the leading
890
+ * button's content; the menu icon holds the expand icon the app provides.
891
+ */
892
+ export declare const splitButtonSkeleton: PartNode<keyof SplitButtonParts, 'showsIcon' | 'showsLabel'>;
893
+ export interface SplitButtonApi<E extends CancelableEvent = MouseEvent> {
894
+ parts: SplitButtonParts;
895
+ /** Whether to render the icon part. */
896
+ showsIcon: () => boolean;
897
+ /** Whether to render the label part. */
898
+ showsLabel: () => boolean;
899
+ /** Attach to the leading button's `click`. */
900
+ handleLeadingClick: (event: E) => void;
901
+ /** Attach to the trailing button's `click`. */
902
+ handleTrailingClick: (event: E) => void;
903
+ }
904
+ export declare function createSplitButton<E extends CancelableEvent = MouseEvent>(options: SplitButtonOptions<E>): SplitButtonApi<E>;
230
905
  export interface SwitchHandleOptions {
231
906
  /** Returns whether the switch is on. */
232
907
  selected: () => boolean;
233
908
  /** Called when a drag ends on the other side. */
234
- onSelectedChange: (selected: boolean) => void;
909
+ onChange: (selected: boolean) => void;
235
910
  }
236
911
  export interface SwitchHandleHandle {
237
912
  detach(): void;
@@ -254,15 +929,148 @@ export declare const switchDefaults: {
254
929
  disabled: false;
255
930
  softDisabled: false;
256
931
  };
932
+ /** What the adapter has to put in the switch, which only the adapter can see. */
933
+ export interface SwitchContent {
934
+ /** Whether there is an icon for the handle while the switch is on. */
935
+ selectedIcon: boolean;
936
+ /** Whether there is an icon for the handle while the switch is off. */
937
+ unselectedIcon: boolean;
938
+ }
257
939
  export interface SwitchOptions {
258
940
  /** Returns the current prop values. The adapter owns the state. */
259
941
  props: () => SwitchProps;
260
- /** Called with the requested value. Adapters wire it to v-model, onChange, and so on. */
261
- onSelectedChange: (selected: boolean) => void;
942
+ /** Returns what content the adapter has. Without it, the handle has no icons. */
943
+ content?: () => SwitchContent;
944
+ /** Called with the requested value. Adapters wire it to v-model, a callback prop, and so on. */
945
+ onChange: (selected: boolean) => void;
262
946
  }
947
+ /**
948
+ * The switch's markup, part by part. `switchSkeleton` says how the adapter nests them.
949
+ */
950
+ export interface SwitchParts {
951
+ root: () => PartAttrs;
952
+ /** Extends the 32px track to the 48px target. */
953
+ touch: () => PartAttrs;
954
+ state: () => PartAttrs;
955
+ handle: () => PartAttrs;
956
+ icon: () => PartAttrs;
957
+ }
958
+ /**
959
+ * How the adapter renders the switch's parts. The state part is the circle where the state layer
960
+ * and the ripple draw. The icon holds the selected icon when `showsSelectedIcon()`, otherwise the
961
+ * unselected one.
962
+ */
963
+ export declare const switchSkeleton: PartNode<keyof SwitchParts, 'showsIcon'>;
263
964
  export interface SwitchApi {
264
- rootAttrs: () => Record<string, string | undefined>;
965
+ parts: SwitchParts;
966
+ /** Whether the handle shows an icon in the current state, which also makes it bigger. */
967
+ showsIcon: () => boolean;
968
+ /** Whether the icon part shows the selected icon: the switch is on and has one. */
969
+ showsSelectedIcon: () => boolean;
265
970
  /** Attach to the root element's `click`. */
266
- handleClick: (event: MouseEvent) => void;
971
+ handleClick: (event: CancelableEvent) => void;
972
+ }
973
+ export declare function createSwitch(options: SwitchOptions): SwitchApi;
974
+ /** M3 tooltip variants: a brief label, or a card with a subhead, text, and actions. */
975
+ export declare const TOOLTIP_VARIANTS: readonly ["plain", "rich"];
976
+ export type TooltipVariant = (typeof TOOLTIP_VARIANTS)[number];
977
+ export interface TooltipProps {
978
+ /** Defaults to `'plain'`. */
979
+ variant?: TooltipVariant;
980
+ /**
981
+ * Rich only: opens on a click on its element rather than on hover or focus, and stays open
982
+ * until the person interacts with something else.
983
+ */
984
+ persistent?: boolean;
985
+ /** Whether the tooltip is shown. */
986
+ open?: boolean;
987
+ /** The side of its element it shows on. Defaults to `'top'` for plain, `'bottom'` for rich. */
988
+ side?: AnchoredSide;
989
+ /** Which edges line up with the element's. Defaults to `'center'` for plain, `'start'` for rich. */
990
+ align?: AnchoredAlign;
991
+ /**
992
+ * Space between the element and the tooltip, in px. Defaults to 4, for an element with a visible
993
+ * edge, such as a button; 8 suits one without, such as text.
994
+ */
995
+ offset?: number;
996
+ }
997
+ export declare const tooltipDefaults: {
998
+ variant: "plain";
999
+ persistent: false;
1000
+ open: false;
1001
+ offset: number;
1002
+ };
1003
+ /** What the adapter has to put in the tooltip, which only the adapter can see. */
1004
+ export interface TooltipContent {
1005
+ /** A subhead above the text (rich only) */
1006
+ subhead: boolean;
1007
+ /** Text buttons below the text (rich only) */
1008
+ actions: boolean;
1009
+ }
1010
+ export interface TooltipOptions {
1011
+ props: () => TooltipProps;
1012
+ /** Without it, the tooltip has only its text. */
1013
+ content?: () => TooltipContent;
1014
+ /** Ids for the parts that others refer to, unique in the document. The adapter makes them. */
1015
+ ids: {
1016
+ root: string;
1017
+ subhead: string;
1018
+ supportingText: string;
1019
+ };
1020
+ }
1021
+ export interface TooltipParts {
1022
+ /**
1023
+ * Wraps the element the tooltip is for, then the tooltip itself, without a box of its own. The
1024
+ * tooltip shows in the top layer, but comes right after its element in the focus order.
1025
+ */
1026
+ trigger: () => PartAttrs;
1027
+ /** The tooltip, shown in the top layer */
1028
+ root: () => PartAttrs;
1029
+ subhead: () => PartAttrs;
1030
+ supportingText: () => PartAttrs;
1031
+ /** Holds the text buttons. */
1032
+ actions: () => PartAttrs;
1033
+ }
1034
+ /** The trigger holds the app's element first, then the tooltip (`tooltipSkeleton`). */
1035
+ export declare const tooltipTriggerSkeleton: PartNode<'trigger'>;
1036
+ type TooltipPart = Exclude<keyof TooltipParts, 'trigger'>;
1037
+ type TooltipWhen = 'showsSubhead' | 'showsActions';
1038
+ /** Spans throughout, so a tooltip may sit inside a paragraph. */
1039
+ export declare const tooltipSkeleton: PartNode<TooltipPart, TooltipWhen>;
1040
+ export interface TooltipPlacement {
1041
+ side: AnchoredSide;
1042
+ align: AnchoredAlign;
1043
+ offset: number;
1044
+ }
1045
+ export interface TooltipApi extends Record<TooltipWhen, () => boolean> {
1046
+ parts: TooltipParts;
1047
+ /**
1048
+ * Whether it holds actions, which take the focus: a rich tooltip with actions is a non-modal
1049
+ * dialog rather than a tooltip.
1050
+ */
1051
+ interactive: () => boolean;
1052
+ /** Whether it opens on a click and stays open (rich only). */
1053
+ persistent: () => boolean;
1054
+ /** Where it shows, with the defaults of its variant filled in. */
1055
+ placement: () => TooltipPlacement;
1056
+ }
1057
+ export declare function createTooltip(options: TooltipOptions): TooltipApi;
1058
+ export interface TooltipDomOptions {
1059
+ /** Where the tooltip shows, read when it opens. */
1060
+ placement: () => TooltipPlacement;
1061
+ /** Whether it opens on a click and stays open. */
1062
+ persistent: () => boolean;
1063
+ /** Whether it holds actions, and so is a dialog whose text describes the element. */
1064
+ interactive: () => boolean;
1065
+ /** The tooltip opens or closes on its own. The adapter sets `open` to match. */
1066
+ onOpenChange: (open: boolean) => void;
1067
+ }
1068
+ export interface TooltipDomHandle {
1069
+ /** Brings the tooltip in line with `open`. Call it once attached, and whenever `open` changes. */
1070
+ sync(open: boolean): void;
1071
+ /** Call it after the element or the tooltip's content changed, to update the element's ARIA. */
1072
+ update(): void;
1073
+ detach(): void;
267
1074
  }
268
- export declare function createSwitch(options: SwitchOptions): SwitchApi;
1075
+ export declare function attachTooltip(trigger: HTMLElement, root: HTMLElement, options: TooltipDomOptions): TooltipDomHandle;
1076
+ export {};