@rio-m3e/core 0.1.0 → 0.2.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 +312 -18
- package/dist/index.js +625 -183
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
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
|
+
* One node of a component's skeleton: the markup an adapter renders, part by part. Adapters
|
|
10
|
+
* render it their own way; contract tests check what they rendered against it. Each component
|
|
11
|
+
* exports its skeleton (`buttonSkeleton`, `switchSkeleton`, ...).
|
|
12
|
+
*
|
|
13
|
+
* `Part` narrows the names to the component's parts, so a typo fails the build. `When` lists the
|
|
14
|
+
* decisions the skeleton may use; leave it out when every part always renders. Skeletons are
|
|
15
|
+
* shared objects, so everything is read-only.
|
|
16
|
+
*/
|
|
17
|
+
export interface PartNode<Part extends string = string, When extends string = never> {
|
|
18
|
+
/** The part, named as in the component's `parts`. */
|
|
19
|
+
readonly part: Part;
|
|
20
|
+
/** The element the part is, as `tagName` in lower case. */
|
|
21
|
+
readonly tag: keyof HTMLElementTagNameMap;
|
|
22
|
+
/**
|
|
23
|
+
* The component API's decision that says whether the part renders, such as `'showsIcon'`.
|
|
24
|
+
* Without it, the part always renders.
|
|
25
|
+
*/
|
|
26
|
+
readonly when?: When;
|
|
27
|
+
/**
|
|
28
|
+
* Content from the app (slots, children) goes inside. The content itself isn't checked against
|
|
29
|
+
* the skeleton; where a component picks between contents, its contract test checks the pick.
|
|
30
|
+
*/
|
|
31
|
+
readonly content?: true;
|
|
32
|
+
readonly children?: readonly PartNode<Part, When>[];
|
|
33
|
+
}
|
|
1
34
|
export declare const BUTTON_VARIANTS: readonly ["filled", "tonal", "outlined", "elevated", "text"];
|
|
2
35
|
export type ButtonVariant = (typeof BUTTON_VARIANTS)[number];
|
|
3
36
|
/** M3E button sizes. `small` is the default. */
|
|
@@ -28,6 +61,11 @@ export interface ButtonProps {
|
|
|
28
61
|
softDisabled?: boolean;
|
|
29
62
|
/** Defaults to `'button'`, so a button inside a form doesn't submit it by accident. */
|
|
30
63
|
type?: 'button' | 'submit' | 'reset';
|
|
64
|
+
/**
|
|
65
|
+
* Identifies the button inside a button group that keeps a selection. Such a group makes the
|
|
66
|
+
* button a toggle and decides whether it is selected.
|
|
67
|
+
*/
|
|
68
|
+
value?: ButtonGroupValue;
|
|
31
69
|
}
|
|
32
70
|
export declare const buttonDefaults: {
|
|
33
71
|
variant: "filled";
|
|
@@ -39,20 +77,121 @@ export declare const buttonDefaults: {
|
|
|
39
77
|
softDisabled: false;
|
|
40
78
|
type: "button";
|
|
41
79
|
};
|
|
80
|
+
/** What the adapter has to put in the button, which only the adapter can see. */
|
|
81
|
+
export interface ButtonContent {
|
|
82
|
+
/** Whether there is a leading icon. */
|
|
83
|
+
icon: boolean;
|
|
84
|
+
}
|
|
42
85
|
export interface ButtonOptions {
|
|
43
86
|
/** Returns the current prop values. The adapter owns the state. */
|
|
44
87
|
props: () => ButtonProps;
|
|
88
|
+
/** Returns what content the adapter has. Without it, the button has no icon. */
|
|
89
|
+
content?: () => ButtonContent;
|
|
45
90
|
/** Called for a click that isn't blocked. */
|
|
46
91
|
onClick: (event: MouseEvent) => void;
|
|
47
|
-
/** Called after an unblocked click on a toggle button, with the
|
|
48
|
-
|
|
92
|
+
/** Called after an unblocked click on a toggle button, with the requested value. */
|
|
93
|
+
onChange: (selected: boolean) => void;
|
|
49
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* The button's markup, part by part. `buttonSkeleton` says how the adapter nests them.
|
|
97
|
+
*/
|
|
98
|
+
export interface ButtonParts {
|
|
99
|
+
root: () => PartAttrs;
|
|
100
|
+
/** Extends small buttons to the 48px target. */
|
|
101
|
+
touch: () => PartAttrs;
|
|
102
|
+
icon: () => PartAttrs;
|
|
103
|
+
label: () => PartAttrs;
|
|
104
|
+
}
|
|
105
|
+
/** How the adapter renders the button's parts. The icon holds the leading icon. */
|
|
106
|
+
export declare const buttonSkeleton: PartNode<keyof ButtonParts, 'showsIcon'>;
|
|
50
107
|
export interface ButtonApi {
|
|
51
|
-
|
|
108
|
+
parts: ButtonParts;
|
|
109
|
+
/** Whether to render the icon part. */
|
|
110
|
+
showsIcon: () => boolean;
|
|
52
111
|
/** Attach to the root element's `click`. */
|
|
53
112
|
handleClick: (event: MouseEvent) => void;
|
|
54
113
|
}
|
|
55
114
|
export declare function createButton(options: ButtonOptions): ButtonApi;
|
|
115
|
+
/**
|
|
116
|
+
* M3E button group variants. A standard group spaces its buttons and lets a pressed button push
|
|
117
|
+
* its neighbors; a connected group joins them with 2px gaps and shared corners, and fills the
|
|
118
|
+
* width it is given.
|
|
119
|
+
*/
|
|
120
|
+
export declare const BUTTON_GROUP_VARIANTS: readonly ["standard", "connected"];
|
|
121
|
+
export type ButtonGroupVariant = (typeof BUTTON_GROUP_VARIANTS)[number];
|
|
122
|
+
/** Whether the group's buttons can be selected: none, one at a time, or any number. */
|
|
123
|
+
export declare const BUTTON_GROUP_SELECTIONS: readonly ["none", "single", "multiple"];
|
|
124
|
+
export type ButtonGroupSelection = (typeof BUTTON_GROUP_SELECTIONS)[number];
|
|
125
|
+
/** Identifies a button inside a selecting group, through its `value` prop. */
|
|
126
|
+
export type ButtonGroupValue = string | number;
|
|
127
|
+
/** The selected value for `single` (or `undefined`), or the selected values for `multiple`. */
|
|
128
|
+
export type ButtonGroupSelected = ButtonGroupValue | ButtonGroupValue[] | undefined;
|
|
129
|
+
export interface ButtonGroupProps {
|
|
130
|
+
/** Defaults to `'standard'`. */
|
|
131
|
+
variant?: ButtonGroupVariant;
|
|
132
|
+
/** The size of every button in the group that doesn't set its own. Defaults to `'small'`. */
|
|
133
|
+
size?: ButtonSize;
|
|
134
|
+
/** The shape of every button in the group that doesn't set its own. Defaults to `'round'`. */
|
|
135
|
+
shape?: ButtonShape;
|
|
136
|
+
/**
|
|
137
|
+
* Turns buttons that have a `value` into toggle buttons whose selection the group keeps.
|
|
138
|
+
* Defaults to `'none'`, where every button acts on its own.
|
|
139
|
+
*/
|
|
140
|
+
selection?: ButtonGroupSelection;
|
|
141
|
+
/** With a selection, keeps at least one button selected: the last one can't be turned off. */
|
|
142
|
+
required?: boolean;
|
|
143
|
+
/** The selected value (`single`) or values (`multiple`). */
|
|
144
|
+
selected?: ButtonGroupSelected;
|
|
145
|
+
}
|
|
146
|
+
export declare const buttonGroupDefaults: {
|
|
147
|
+
variant: "standard";
|
|
148
|
+
size: "small";
|
|
149
|
+
shape: "round";
|
|
150
|
+
selection: "none";
|
|
151
|
+
required: false;
|
|
152
|
+
};
|
|
153
|
+
export interface ButtonGroupOptions {
|
|
154
|
+
/** Returns the current prop values. The adapter owns the state. */
|
|
155
|
+
props: () => ButtonGroupProps;
|
|
156
|
+
/** Called with the requested selection. Adapters wire it to v-model, a callback prop, and so on. */
|
|
157
|
+
onChange: (selected: ButtonGroupSelected) => void;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The button group's markup. `buttonGroupSkeleton` says how the adapter renders it.
|
|
161
|
+
*/
|
|
162
|
+
export interface ButtonGroupParts {
|
|
163
|
+
root: () => PartAttrs;
|
|
164
|
+
}
|
|
165
|
+
/** How the adapter renders the button group: one root, with the buttons as its direct children. */
|
|
166
|
+
export declare const buttonGroupSkeleton: PartNode<keyof ButtonGroupParts>;
|
|
167
|
+
export interface ButtonGroupApi {
|
|
168
|
+
parts: ButtonGroupParts;
|
|
169
|
+
/** The size for buttons that don't set their own, with the default filled in. */
|
|
170
|
+
size: () => ButtonSize;
|
|
171
|
+
/** The shape for buttons that don't set their own, with the default filled in. */
|
|
172
|
+
shape: () => ButtonShape;
|
|
173
|
+
/** The selection mode, with the default filled in. */
|
|
174
|
+
selection: () => ButtonGroupSelection;
|
|
175
|
+
/** Whether the button with this value is selected. */
|
|
176
|
+
isSelected: (value: ButtonGroupValue) => boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Requests turning the button with this value on or off. The group applies its selection rules:
|
|
179
|
+
* a single selection turns the previous button off, and `required` keeps the last one on.
|
|
180
|
+
*/
|
|
181
|
+
select: (value: ButtonGroupValue, selected: boolean) => void;
|
|
182
|
+
}
|
|
183
|
+
export declare function createButtonGroup(options: ButtonGroupOptions): ButtonGroupApi;
|
|
184
|
+
export interface ButtonGroupInteractionOptions {
|
|
185
|
+
/**
|
|
186
|
+
* How much wider a pressed button gets, as a share of its width. Each neighbor gives up at most
|
|
187
|
+
* the same share of its own width. Defaults to 0.15, the spec's 15%.
|
|
188
|
+
*/
|
|
189
|
+
pressedWidthRatio?: number;
|
|
190
|
+
}
|
|
191
|
+
export interface ButtonGroupHandle {
|
|
192
|
+
detach(): void;
|
|
193
|
+
}
|
|
194
|
+
export declare function attachButtonGroup(root: HTMLElement, options?: ButtonGroupInteractionOptions): ButtonGroupHandle;
|
|
56
195
|
export declare const ICON_BUTTON_VARIANTS: readonly ["filled", "tonal", "outlined", "standard"];
|
|
57
196
|
export type IconButtonVariant = (typeof ICON_BUTTON_VARIANTS)[number];
|
|
58
197
|
/** M3E icon button widths. `default` makes a square (or circle) as wide as it is tall. */
|
|
@@ -82,6 +221,8 @@ export interface IconButtonProps {
|
|
|
82
221
|
softDisabled?: boolean;
|
|
83
222
|
/** Defaults to `'button'`, so it doesn't submit a form by accident. */
|
|
84
223
|
type?: 'button' | 'submit' | 'reset';
|
|
224
|
+
/** Identifies the button inside a button group that keeps a selection, as on the button. */
|
|
225
|
+
value?: ButtonGroupValue;
|
|
85
226
|
}
|
|
86
227
|
export declare const iconButtonDefaults: {
|
|
87
228
|
variant: "filled";
|
|
@@ -94,15 +235,43 @@ export declare const iconButtonDefaults: {
|
|
|
94
235
|
softDisabled: false;
|
|
95
236
|
type: "button";
|
|
96
237
|
};
|
|
238
|
+
/** What the adapter has to put in the icon button, which only the adapter can see. */
|
|
239
|
+
export interface IconButtonContent {
|
|
240
|
+
/** Whether there is an icon for the selected state. */
|
|
241
|
+
selectedIcon: boolean;
|
|
242
|
+
}
|
|
97
243
|
export interface IconButtonOptions {
|
|
98
244
|
/** Returns the current prop values. The adapter owns the state. */
|
|
99
245
|
props: () => IconButtonProps;
|
|
246
|
+
/** Returns what content the adapter has. Without it, there is no selected icon. */
|
|
247
|
+
content?: () => IconButtonContent;
|
|
100
248
|
/** Called for a click that isn't blocked. */
|
|
101
249
|
onClick: (event: MouseEvent) => void;
|
|
102
|
-
/** Called after an unblocked click on a toggle button, with the
|
|
103
|
-
|
|
250
|
+
/** Called after an unblocked click on a toggle button, with the requested value. */
|
|
251
|
+
onChange: (selected: boolean) => void;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The icon button's markup, part by part. `iconButtonSkeleton` says how the adapter nests them.
|
|
255
|
+
*/
|
|
256
|
+
export interface IconButtonParts {
|
|
257
|
+
root: () => PartAttrs;
|
|
258
|
+
/** Extends small icon buttons to the 48px target. */
|
|
259
|
+
touch: () => PartAttrs;
|
|
260
|
+
icon: () => PartAttrs;
|
|
104
261
|
}
|
|
105
|
-
|
|
262
|
+
/**
|
|
263
|
+
* How the adapter renders the icon button's parts. The icon holds the selected icon when
|
|
264
|
+
* `showsSelectedIcon()`, otherwise the default one.
|
|
265
|
+
*/
|
|
266
|
+
export declare const iconButtonSkeleton: PartNode<keyof IconButtonParts>;
|
|
267
|
+
export interface IconButtonApi {
|
|
268
|
+
parts: IconButtonParts;
|
|
269
|
+
/** Whether the icon part shows the selected icon instead of the default one. */
|
|
270
|
+
showsSelectedIcon: () => boolean;
|
|
271
|
+
/** Attach to the root element's `click`. */
|
|
272
|
+
handleClick: (event: MouseEvent) => void;
|
|
273
|
+
}
|
|
274
|
+
export declare function createIconButton(options: IconButtonOptions): IconButtonApi;
|
|
106
275
|
export interface DragInfo {
|
|
107
276
|
/** Distance from where the press started, in px. */
|
|
108
277
|
dx: number;
|
|
@@ -147,22 +316,21 @@ export declare function attachRipple(element: HTMLElement, options?: RippleOptio
|
|
|
147
316
|
export interface ShapeMorphOptions {
|
|
148
317
|
/** The motion token whose spring drives the morph. Defaults to `'fast-spatial'`. */
|
|
149
318
|
token?: string;
|
|
150
|
-
/** Attributes whose changes can move the target. Defaults to Rio's state attributes. */
|
|
151
|
-
watch?: string[];
|
|
152
319
|
}
|
|
153
320
|
export interface ShapeMorphHandle {
|
|
154
321
|
detach(): void;
|
|
155
322
|
}
|
|
156
323
|
export declare function attachShapeMorph(element: HTMLElement, options?: ShapeMorphOptions): ShapeMorphHandle;
|
|
157
324
|
export interface SpringPropertyOptions {
|
|
158
|
-
/**
|
|
325
|
+
/**
|
|
326
|
+
* A registered custom property that holds the target for the current state: a <length>, read
|
|
327
|
+
* in px, or an <angle>, read in deg. The stylesheet has to transition it (see above).
|
|
328
|
+
*/
|
|
159
329
|
target: string;
|
|
160
|
-
/** Writes the animated value
|
|
330
|
+
/** Writes the animated value (px or deg), or clears it with `undefined` at rest. */
|
|
161
331
|
apply: (value: number | undefined) => void;
|
|
162
332
|
/** The motion token whose spring drives the value. Defaults to `'fast-spatial'`. */
|
|
163
333
|
token?: string;
|
|
164
|
-
/** Attributes whose changes can move the target. Defaults to Rio's state attributes. */
|
|
165
|
-
watch?: string[];
|
|
166
334
|
}
|
|
167
335
|
export interface SpringPropertyHandle {
|
|
168
336
|
/** The current animated value. */
|
|
@@ -173,8 +341,6 @@ export interface SpringPropertyHandle {
|
|
|
173
341
|
release(velocity?: number): void;
|
|
174
342
|
detach(): void;
|
|
175
343
|
}
|
|
176
|
-
/** The data attributes Rio's components use for state. */
|
|
177
|
-
export declare const STATE_ATTRIBUTES: string[];
|
|
178
344
|
export declare function attachSpringProperty(element: HTMLElement, options: SpringPropertyOptions): SpringPropertyHandle;
|
|
179
345
|
export interface SpringParams {
|
|
180
346
|
/** 1 settles without overshoot; lower values bounce, higher values approach more slowly. */
|
|
@@ -220,6 +386,11 @@ export interface Spring {
|
|
|
220
386
|
}
|
|
221
387
|
/** A spring that animates a single number with `requestAnimationFrame`. */
|
|
222
388
|
export declare function createSpring(options: SpringOptions): Spring;
|
|
389
|
+
/**
|
|
390
|
+
* Whether the user asked for reduced motion. Spatial springs should jump straight to their target
|
|
391
|
+
* (or be skipped) when it is on; see `docs/foundations.md`, section 3.
|
|
392
|
+
*/
|
|
393
|
+
export declare function prefersReducedMotion(): boolean;
|
|
223
394
|
/**
|
|
224
395
|
* Reads a motion token's spring from CSS variables on an element, for example
|
|
225
396
|
* `--rio-motion-fast-spatial-damping` and `-stiffness`. Reading them from the element, rather than
|
|
@@ -227,11 +398,104 @@ export declare function createSpring(options: SpringOptions): Spring;
|
|
|
227
398
|
* Returns `undefined` when the theme doesn't define them.
|
|
228
399
|
*/
|
|
229
400
|
export declare function readSpringParams(element: Element, token: string): SpringParams | undefined;
|
|
401
|
+
export interface SplitButtonMenuIconHandle {
|
|
402
|
+
detach(): void;
|
|
403
|
+
}
|
|
404
|
+
/** Attach to the trailing button. */
|
|
405
|
+
export declare function attachSplitButtonMenuIcon(trailing: HTMLElement): SplitButtonMenuIconHandle;
|
|
406
|
+
/** Split button colors: the button's, without text. */
|
|
407
|
+
export declare const SPLIT_BUTTON_VARIANTS: readonly ["filled", "tonal", "outlined", "elevated"];
|
|
408
|
+
export type SplitButtonVariant = (typeof SPLIT_BUTTON_VARIANTS)[number];
|
|
409
|
+
/** What the trailing button opens, for `aria-haspopup`. */
|
|
410
|
+
export type SplitButtonPopup = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';
|
|
411
|
+
export interface SplitButtonProps {
|
|
412
|
+
/**
|
|
413
|
+
* Names the trailing button. It should say that more choices for the leading action are there,
|
|
414
|
+
* such as "More watch options" next to "Watch later".
|
|
415
|
+
*/
|
|
416
|
+
menuLabel: string;
|
|
417
|
+
/**
|
|
418
|
+
* Names the leading button when it has no label text, only an icon. Ignored when there is label
|
|
419
|
+
* text, so the visible label stays the name (WCAG 2.5.3).
|
|
420
|
+
*/
|
|
421
|
+
label?: string;
|
|
422
|
+
/** Defaults to `'filled'`. */
|
|
423
|
+
variant?: SplitButtonVariant;
|
|
424
|
+
/** Defaults to `'small'`. */
|
|
425
|
+
size?: ButtonSize;
|
|
426
|
+
/** Whether what the trailing button opens is open. */
|
|
427
|
+
open?: boolean;
|
|
428
|
+
/** What the trailing button opens. Defaults to `'menu'`. */
|
|
429
|
+
popup?: SplitButtonPopup;
|
|
430
|
+
/** Native disabled, on both buttons: not focusable and not pressable. The spec's behavior. */
|
|
431
|
+
disabled?: boolean;
|
|
432
|
+
/** Disabled but still focusable, on both buttons, as on the button. Deviates from the spec. */
|
|
433
|
+
softDisabled?: boolean;
|
|
434
|
+
/** The leading button's type. Defaults to `'button'`; the trailing button is always `'button'`. */
|
|
435
|
+
type?: 'button' | 'submit' | 'reset';
|
|
436
|
+
}
|
|
437
|
+
export declare const splitButtonDefaults: {
|
|
438
|
+
variant: "filled";
|
|
439
|
+
size: "small";
|
|
440
|
+
open: false;
|
|
441
|
+
popup: "menu";
|
|
442
|
+
disabled: false;
|
|
443
|
+
softDisabled: false;
|
|
444
|
+
type: "button";
|
|
445
|
+
};
|
|
446
|
+
/** What the adapter has to put in the leading button, which only the adapter can see. */
|
|
447
|
+
export interface SplitButtonContent {
|
|
448
|
+
/** Whether there is a leading icon. */
|
|
449
|
+
icon: boolean;
|
|
450
|
+
/** Whether there is label text. */
|
|
451
|
+
label: boolean;
|
|
452
|
+
}
|
|
453
|
+
export interface SplitButtonOptions {
|
|
454
|
+
/** Returns the current prop values. The adapter owns the state. */
|
|
455
|
+
props: () => SplitButtonProps;
|
|
456
|
+
/** Returns what content the adapter has. Without it, the leading button has only a label. */
|
|
457
|
+
content?: () => SplitButtonContent;
|
|
458
|
+
/** Called for a click on the leading button that isn't blocked. */
|
|
459
|
+
onClick: (event: MouseEvent) => void;
|
|
460
|
+
/** Called with the requested `open` when the trailing button is clicked and isn't blocked. */
|
|
461
|
+
onChange: (open: boolean) => void;
|
|
462
|
+
}
|
|
463
|
+
/** The split button's markup, part by part. `splitButtonSkeleton` says how the adapter nests them. */
|
|
464
|
+
export interface SplitButtonParts {
|
|
465
|
+
root: () => PartAttrs;
|
|
466
|
+
/** The main action, a button with an icon, label text, or both. */
|
|
467
|
+
leading: () => PartAttrs;
|
|
468
|
+
/** The button that opens the menu. */
|
|
469
|
+
trailing: () => PartAttrs;
|
|
470
|
+
/** Extends each button to the 48px target. Both buttons have one. */
|
|
471
|
+
touch: () => PartAttrs;
|
|
472
|
+
icon: () => PartAttrs;
|
|
473
|
+
label: () => PartAttrs;
|
|
474
|
+
/** Holds the expand icon, which turns when the menu opens. */
|
|
475
|
+
menuIcon: () => PartAttrs;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* How the adapter renders the split button's parts. The icon and the label hold the leading
|
|
479
|
+
* button's content; the menu icon holds the expand icon the app provides.
|
|
480
|
+
*/
|
|
481
|
+
export declare const splitButtonSkeleton: PartNode<keyof SplitButtonParts, 'showsIcon' | 'showsLabel'>;
|
|
482
|
+
export interface SplitButtonApi {
|
|
483
|
+
parts: SplitButtonParts;
|
|
484
|
+
/** Whether to render the icon part. */
|
|
485
|
+
showsIcon: () => boolean;
|
|
486
|
+
/** Whether to render the label part. */
|
|
487
|
+
showsLabel: () => boolean;
|
|
488
|
+
/** Attach to the leading button's `click`. */
|
|
489
|
+
handleLeadingClick: (event: MouseEvent) => void;
|
|
490
|
+
/** Attach to the trailing button's `click`. */
|
|
491
|
+
handleTrailingClick: (event: MouseEvent) => void;
|
|
492
|
+
}
|
|
493
|
+
export declare function createSplitButton(options: SplitButtonOptions): SplitButtonApi;
|
|
230
494
|
export interface SwitchHandleOptions {
|
|
231
495
|
/** Returns whether the switch is on. */
|
|
232
496
|
selected: () => boolean;
|
|
233
497
|
/** Called when a drag ends on the other side. */
|
|
234
|
-
|
|
498
|
+
onChange: (selected: boolean) => void;
|
|
235
499
|
}
|
|
236
500
|
export interface SwitchHandleHandle {
|
|
237
501
|
detach(): void;
|
|
@@ -254,14 +518,44 @@ export declare const switchDefaults: {
|
|
|
254
518
|
disabled: false;
|
|
255
519
|
softDisabled: false;
|
|
256
520
|
};
|
|
521
|
+
/** What the adapter has to put in the switch, which only the adapter can see. */
|
|
522
|
+
export interface SwitchContent {
|
|
523
|
+
/** Whether there is an icon for the handle while the switch is on. */
|
|
524
|
+
selectedIcon: boolean;
|
|
525
|
+
/** Whether there is an icon for the handle while the switch is off. */
|
|
526
|
+
unselectedIcon: boolean;
|
|
527
|
+
}
|
|
257
528
|
export interface SwitchOptions {
|
|
258
529
|
/** Returns the current prop values. The adapter owns the state. */
|
|
259
530
|
props: () => SwitchProps;
|
|
260
|
-
/**
|
|
261
|
-
|
|
531
|
+
/** Returns what content the adapter has. Without it, the handle has no icons. */
|
|
532
|
+
content?: () => SwitchContent;
|
|
533
|
+
/** Called with the requested value. Adapters wire it to v-model, a callback prop, and so on. */
|
|
534
|
+
onChange: (selected: boolean) => void;
|
|
262
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* The switch's markup, part by part. `switchSkeleton` says how the adapter nests them.
|
|
538
|
+
*/
|
|
539
|
+
export interface SwitchParts {
|
|
540
|
+
root: () => PartAttrs;
|
|
541
|
+
/** Extends the 32px track to the 48px target. */
|
|
542
|
+
touch: () => PartAttrs;
|
|
543
|
+
state: () => PartAttrs;
|
|
544
|
+
handle: () => PartAttrs;
|
|
545
|
+
icon: () => PartAttrs;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* How the adapter renders the switch's parts. The state part is the circle where the state layer
|
|
549
|
+
* and the ripple draw. The icon holds the selected icon when `showsSelectedIcon()`, otherwise the
|
|
550
|
+
* unselected one.
|
|
551
|
+
*/
|
|
552
|
+
export declare const switchSkeleton: PartNode<keyof SwitchParts, 'showsIcon'>;
|
|
263
553
|
export interface SwitchApi {
|
|
264
|
-
|
|
554
|
+
parts: SwitchParts;
|
|
555
|
+
/** Whether the handle shows an icon in the current state, which also makes it bigger. */
|
|
556
|
+
showsIcon: () => boolean;
|
|
557
|
+
/** Whether the icon part shows the selected icon: the switch is on and has one. */
|
|
558
|
+
showsSelectedIcon: () => boolean;
|
|
265
559
|
/** Attach to the root element's `click`. */
|
|
266
560
|
handleClick: (event: MouseEvent) => void;
|
|
267
561
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,295 @@
|
|
|
1
|
+
// src/utils/object.ts
|
|
2
|
+
/**
|
|
3
|
+
* A copy without the properties that are explicitly `undefined`, so spreading it over defaults
|
|
4
|
+
* keeps the defaults: `{ ...defaults, ...omitUndefined(props) }`.
|
|
5
|
+
*/
|
|
6
|
+
function omitUndefined(object) {
|
|
7
|
+
return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== void 0));
|
|
8
|
+
}
|
|
9
|
+
// src/button-group/button-group.ts
|
|
10
|
+
/**
|
|
11
|
+
* M3E button group variants. A standard group spaces its buttons and lets a pressed button push
|
|
12
|
+
* its neighbors; a connected group joins them with 2px gaps and shared corners, and fills the
|
|
13
|
+
* width it is given.
|
|
14
|
+
*/
|
|
15
|
+
const BUTTON_GROUP_VARIANTS = ["standard", "connected"];
|
|
16
|
+
/** Whether the group's buttons can be selected: none, one at a time, or any number. */
|
|
17
|
+
const BUTTON_GROUP_SELECTIONS = [
|
|
18
|
+
"none",
|
|
19
|
+
"single",
|
|
20
|
+
"multiple"
|
|
21
|
+
];
|
|
22
|
+
const buttonGroupDefaults = {
|
|
23
|
+
variant: "standard",
|
|
24
|
+
size: "small",
|
|
25
|
+
shape: "round",
|
|
26
|
+
selection: "none",
|
|
27
|
+
required: false
|
|
28
|
+
};
|
|
29
|
+
/** How the adapter renders the button group: one root, with the buttons as its direct children. */
|
|
30
|
+
const buttonGroupSkeleton = {
|
|
31
|
+
part: "root",
|
|
32
|
+
tag: "div",
|
|
33
|
+
content: true
|
|
34
|
+
};
|
|
35
|
+
const valuesOf = (selected) => selected === void 0 ? [] : Array.isArray(selected) ? selected : [selected];
|
|
36
|
+
function createButtonGroup(options) {
|
|
37
|
+
const props = () => ({
|
|
38
|
+
...buttonGroupDefaults,
|
|
39
|
+
...omitUndefined(options.props())
|
|
40
|
+
});
|
|
41
|
+
const isSelected = (value) => {
|
|
42
|
+
const p = props();
|
|
43
|
+
return p.selection !== "none" && valuesOf(p.selected).includes(value);
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
parts: { root() {
|
|
47
|
+
const p = props();
|
|
48
|
+
return {
|
|
49
|
+
class: "rio-button-group",
|
|
50
|
+
role: "group",
|
|
51
|
+
"data-variant": p.variant,
|
|
52
|
+
"data-size": p.size,
|
|
53
|
+
"data-shape": p.shape
|
|
54
|
+
};
|
|
55
|
+
} },
|
|
56
|
+
size: () => props().size,
|
|
57
|
+
shape: () => props().shape,
|
|
58
|
+
selection: () => props().selection,
|
|
59
|
+
isSelected,
|
|
60
|
+
select(value, selected) {
|
|
61
|
+
const p = props();
|
|
62
|
+
if (p.selection === "none" || isSelected(value) === selected) return;
|
|
63
|
+
const current = valuesOf(p.selected);
|
|
64
|
+
if (!selected && p.required && current.length === 1) return;
|
|
65
|
+
if (p.selection === "single") options.onChange(selected ? value : void 0);
|
|
66
|
+
else options.onChange(selected ? [...current, value] : current.filter((other) => other !== value));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// src/primitives/spring/spring.ts
|
|
71
|
+
/**
|
|
72
|
+
* Where a spring is `t` seconds after it starts at `from` with `velocity`, heading to `to`.
|
|
73
|
+
*/
|
|
74
|
+
function solveSpring({ dampingRatio: zeta, stiffness }, from, to, velocity, t) {
|
|
75
|
+
const w0 = Math.sqrt(stiffness);
|
|
76
|
+
const x0 = from - to;
|
|
77
|
+
const v0 = velocity;
|
|
78
|
+
if (Math.abs(zeta - 1) < 1e-6) {
|
|
79
|
+
const b = v0 + w0 * x0;
|
|
80
|
+
const decay = Math.exp(-w0 * t);
|
|
81
|
+
return {
|
|
82
|
+
value: to + (x0 + b * t) * decay,
|
|
83
|
+
velocity: (b - w0 * (x0 + b * t)) * decay
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (zeta < 1) {
|
|
87
|
+
const a = zeta * w0;
|
|
88
|
+
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
89
|
+
const b = (v0 + a * x0) / wd;
|
|
90
|
+
const decay = Math.exp(-a * t);
|
|
91
|
+
const cos = Math.cos(wd * t);
|
|
92
|
+
const sin = Math.sin(wd * t);
|
|
93
|
+
return {
|
|
94
|
+
value: to + decay * (x0 * cos + b * sin),
|
|
95
|
+
velocity: decay * ((wd * b - a * x0) * cos - (a * b + wd * x0) * sin)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const root = w0 * Math.sqrt(zeta * zeta - 1);
|
|
99
|
+
const r1 = -zeta * w0 + root;
|
|
100
|
+
const r2 = -zeta * w0 - root;
|
|
101
|
+
const c2 = (v0 - r1 * x0) / (r2 - r1);
|
|
102
|
+
const c1 = x0 - c2;
|
|
103
|
+
const e1 = Math.exp(r1 * t);
|
|
104
|
+
const e2 = Math.exp(r2 * t);
|
|
105
|
+
return {
|
|
106
|
+
value: to + c1 * e1 + c2 * e2,
|
|
107
|
+
velocity: r1 * c1 * e1 + r2 * c2 * e2
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const now = () => performance.now() / 1e3;
|
|
111
|
+
/** A spring that animates a single number with `requestAnimationFrame`. */
|
|
112
|
+
function createSpring(options) {
|
|
113
|
+
const precision = options.precision ?? .01;
|
|
114
|
+
let from = options.value;
|
|
115
|
+
let target = options.value;
|
|
116
|
+
let startVelocity = 0;
|
|
117
|
+
let params;
|
|
118
|
+
let startedAt = 0;
|
|
119
|
+
let frame;
|
|
120
|
+
const state = () => params && frame !== void 0 ? solveSpring(params, from, target, startVelocity, now() - startedAt) : {
|
|
121
|
+
value: target,
|
|
122
|
+
velocity: 0
|
|
123
|
+
};
|
|
124
|
+
const settle = () => {
|
|
125
|
+
if (frame !== void 0) cancelAnimationFrame(frame);
|
|
126
|
+
frame = void 0;
|
|
127
|
+
from = target;
|
|
128
|
+
startVelocity = 0;
|
|
129
|
+
options.onUpdate(target);
|
|
130
|
+
options.onRest?.();
|
|
131
|
+
};
|
|
132
|
+
const tick = () => {
|
|
133
|
+
const { value, velocity } = state();
|
|
134
|
+
if (Math.abs(value - target) < precision && Math.abs(velocity) < precision * 10) {
|
|
135
|
+
settle();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
options.onUpdate(value);
|
|
139
|
+
frame = requestAnimationFrame(tick);
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
get value() {
|
|
143
|
+
return state().value;
|
|
144
|
+
},
|
|
145
|
+
get velocity() {
|
|
146
|
+
return state().velocity;
|
|
147
|
+
},
|
|
148
|
+
get target() {
|
|
149
|
+
return target;
|
|
150
|
+
},
|
|
151
|
+
get moving() {
|
|
152
|
+
return frame !== void 0;
|
|
153
|
+
},
|
|
154
|
+
to(next, nextParams, velocity) {
|
|
155
|
+
const current = state();
|
|
156
|
+
from = current.value;
|
|
157
|
+
startVelocity = velocity ?? current.velocity;
|
|
158
|
+
target = next;
|
|
159
|
+
params = nextParams;
|
|
160
|
+
startedAt = now();
|
|
161
|
+
if (frame === void 0) frame = requestAnimationFrame(tick);
|
|
162
|
+
},
|
|
163
|
+
jump(value) {
|
|
164
|
+
target = value;
|
|
165
|
+
settle();
|
|
166
|
+
},
|
|
167
|
+
stop() {
|
|
168
|
+
const { value } = state();
|
|
169
|
+
if (frame !== void 0) cancelAnimationFrame(frame);
|
|
170
|
+
frame = void 0;
|
|
171
|
+
from = target = value;
|
|
172
|
+
startVelocity = 0;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Whether the user asked for reduced motion. Spatial springs should jump straight to their target
|
|
178
|
+
* (or be skipped) when it is on; see `docs/foundations.md`, section 3.
|
|
179
|
+
*/
|
|
180
|
+
function prefersReducedMotion() {
|
|
181
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Reads a motion token's spring from CSS variables on an element, for example
|
|
185
|
+
* `--rio-motion-fast-spatial-damping` and `-stiffness`. Reading them from the element, rather than
|
|
186
|
+
* from the tokens package, lets a subtree that swaps motion schemes change its springs too.
|
|
187
|
+
* Returns `undefined` when the theme doesn't define them.
|
|
188
|
+
*/
|
|
189
|
+
function readSpringParams(element, token) {
|
|
190
|
+
const style = getComputedStyle(element);
|
|
191
|
+
const dampingRatio = parseFloat(style.getPropertyValue(`--rio-motion-${token}-damping`));
|
|
192
|
+
const stiffness = parseFloat(style.getPropertyValue(`--rio-motion-${token}-stiffness`));
|
|
193
|
+
return Number.isFinite(dampingRatio) && Number.isFinite(stiffness) ? {
|
|
194
|
+
dampingRatio,
|
|
195
|
+
stiffness
|
|
196
|
+
} : void 0;
|
|
197
|
+
}
|
|
198
|
+
// src/button-group/interaction.ts
|
|
199
|
+
const PRESSED_WIDTH_RATIO = .15;
|
|
200
|
+
function attachButtonGroup(root, options = {}) {
|
|
201
|
+
const ratio = options.pressedWidthRatio ?? PRESSED_WIDTH_RATIO;
|
|
202
|
+
const items = () => [...root.children].filter((el) => el instanceof HTMLElement);
|
|
203
|
+
const onKeydown = (event) => {
|
|
204
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
205
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
206
|
+
const focusable = items().filter((el) => !el.matches(":disabled") && el.tabIndex >= 0);
|
|
207
|
+
const index = focusable.findIndex((el) => el.contains(document.activeElement));
|
|
208
|
+
if (index === -1) return;
|
|
209
|
+
const next = focusable[index + (event.key === "ArrowRight" !== (getComputedStyle(root).direction === "rtl") ? 1 : -1)];
|
|
210
|
+
if (!next) return;
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
next.focus();
|
|
213
|
+
};
|
|
214
|
+
root.addEventListener("keydown", onKeydown);
|
|
215
|
+
const springs = /* @__PURE__ */ new Map();
|
|
216
|
+
const natural = /* @__PURE__ */ new Map();
|
|
217
|
+
const setWidth = (el, value) => value === void 0 ? el.style.removeProperty("inline-size") : el.style.setProperty("inline-size", `${value}px`);
|
|
218
|
+
const springOf = (el) => {
|
|
219
|
+
let spring = springs.get(el);
|
|
220
|
+
if (!spring) {
|
|
221
|
+
spring = createSpring({
|
|
222
|
+
value: natural.get(el),
|
|
223
|
+
onUpdate: (value) => setWidth(el, value),
|
|
224
|
+
onRest: () => {
|
|
225
|
+
if (springs.get(el)?.target === natural.get(el)) setWidth(el, void 0);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
springs.set(el, spring);
|
|
229
|
+
}
|
|
230
|
+
return spring;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* A button's own width, without the width a spring wrote on it. The inline width comes off and
|
|
234
|
+
* goes back within the same task, so it never shows.
|
|
235
|
+
*/
|
|
236
|
+
const measure = (el) => {
|
|
237
|
+
const inline = el.style.inlineSize;
|
|
238
|
+
el.style.removeProperty("inline-size");
|
|
239
|
+
const width = el.getBoundingClientRect().width;
|
|
240
|
+
el.style.setProperty("inline-size", inline);
|
|
241
|
+
return width;
|
|
242
|
+
};
|
|
243
|
+
/** Sets every button's target width. `instant` skips the springs, for changes of size. */
|
|
244
|
+
const layout = (instant = false) => {
|
|
245
|
+
const all = items();
|
|
246
|
+
for (const el of all) natural.set(el, measure(el));
|
|
247
|
+
const targets = new Map(all.map((el) => [el, natural.get(el)]));
|
|
248
|
+
const pressed = all.findIndex((el) => el.hasAttribute("data-pressed"));
|
|
249
|
+
const standard = root.getAttribute("data-variant") !== "connected";
|
|
250
|
+
if (pressed !== -1 && standard && !prefersReducedMotion()) {
|
|
251
|
+
const growth = natural.get(all[pressed]) * ratio;
|
|
252
|
+
const neighbors = [all[pressed - 1], all[pressed + 1]].filter((el) => el !== void 0);
|
|
253
|
+
let gained = 0;
|
|
254
|
+
for (const el of neighbors) {
|
|
255
|
+
const width = natural.get(el);
|
|
256
|
+
const given = Math.min(growth / neighbors.length, width * ratio);
|
|
257
|
+
targets.set(el, width - given);
|
|
258
|
+
gained += given;
|
|
259
|
+
}
|
|
260
|
+
targets.set(all[pressed], natural.get(all[pressed]) + gained);
|
|
261
|
+
}
|
|
262
|
+
const params = readSpringParams(root, "fast-spatial");
|
|
263
|
+
for (const [el, target] of targets) {
|
|
264
|
+
const spring = springOf(el);
|
|
265
|
+
if (!el.style.inlineSize && spring.value !== natural.get(el)) spring.jump(natural.get(el));
|
|
266
|
+
if (spring.target === target) continue;
|
|
267
|
+
if (params && !instant && !prefersReducedMotion()) spring.to(target, params);
|
|
268
|
+
else spring.jump(target);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const observer = new MutationObserver((mutations) => {
|
|
272
|
+
const own = mutations.filter((m) => m.target.parentElement === root);
|
|
273
|
+
if (own.length > 0) layout(own.some((m) => m.attributeName !== "data-pressed"));
|
|
274
|
+
});
|
|
275
|
+
observer.observe(root, {
|
|
276
|
+
subtree: true,
|
|
277
|
+
attributes: true,
|
|
278
|
+
attributeFilter: [
|
|
279
|
+
"data-pressed",
|
|
280
|
+
"data-size",
|
|
281
|
+
"data-width"
|
|
282
|
+
]
|
|
283
|
+
});
|
|
284
|
+
return { detach() {
|
|
285
|
+
root.removeEventListener("keydown", onKeydown);
|
|
286
|
+
observer.disconnect();
|
|
287
|
+
for (const [el, spring] of springs) {
|
|
288
|
+
spring.stop();
|
|
289
|
+
setWidth(el, void 0);
|
|
290
|
+
}
|
|
291
|
+
} };
|
|
292
|
+
}
|
|
1
293
|
// src/button/button.ts
|
|
2
294
|
const BUTTON_VARIANTS = [
|
|
3
295
|
"filled",
|
|
@@ -24,28 +316,63 @@ const buttonDefaults = {
|
|
|
24
316
|
softDisabled: false,
|
|
25
317
|
type: "button"
|
|
26
318
|
};
|
|
319
|
+
/** How the adapter renders the button's parts. The icon holds the leading icon. */
|
|
320
|
+
const buttonSkeleton = {
|
|
321
|
+
part: "root",
|
|
322
|
+
tag: "button",
|
|
323
|
+
children: [
|
|
324
|
+
{
|
|
325
|
+
part: "touch",
|
|
326
|
+
tag: "span"
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
part: "icon",
|
|
330
|
+
tag: "span",
|
|
331
|
+
when: "showsIcon",
|
|
332
|
+
content: true
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
part: "label",
|
|
336
|
+
tag: "span",
|
|
337
|
+
content: true
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
};
|
|
27
341
|
const isToggle = (p) => !!p.toggle && p.variant !== "text";
|
|
28
342
|
/** Defaults under the given props, skipping props that are explicitly `undefined`. */
|
|
29
343
|
const resolve = (props) => ({
|
|
30
344
|
...buttonDefaults,
|
|
31
|
-
...
|
|
345
|
+
...omitUndefined(props)
|
|
32
346
|
});
|
|
33
347
|
function createButton(options) {
|
|
34
348
|
return {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
349
|
+
parts: {
|
|
350
|
+
root() {
|
|
351
|
+
const p = resolve(options.props());
|
|
352
|
+
const toggle = isToggle(p);
|
|
353
|
+
return {
|
|
354
|
+
class: "rio-button rio-state-layer rio-focus-ring",
|
|
355
|
+
type: p.type,
|
|
356
|
+
disabled: p.disabled ? "" : void 0,
|
|
357
|
+
"aria-disabled": p.softDisabled && !p.disabled ? "true" : void 0,
|
|
358
|
+
"aria-pressed": toggle ? String(p.selected) : void 0,
|
|
359
|
+
"data-variant": p.variant,
|
|
360
|
+
"data-size": p.size,
|
|
361
|
+
"data-shape": p.shape,
|
|
362
|
+
"data-selected": toggle ? String(p.selected) : void 0
|
|
363
|
+
};
|
|
364
|
+
},
|
|
365
|
+
touch: () => ({
|
|
366
|
+
class: "rio-button__touch",
|
|
367
|
+
"aria-hidden": "true"
|
|
368
|
+
}),
|
|
369
|
+
icon: () => ({
|
|
370
|
+
class: "rio-button__icon",
|
|
371
|
+
"aria-hidden": "true"
|
|
372
|
+
}),
|
|
373
|
+
label: () => ({ class: "rio-button__label" })
|
|
48
374
|
},
|
|
375
|
+
showsIcon: () => !!options.content?.().icon,
|
|
49
376
|
handleClick(event) {
|
|
50
377
|
const p = options.props();
|
|
51
378
|
if (p.disabled || p.softDisabled) {
|
|
@@ -53,7 +380,7 @@ function createButton(options) {
|
|
|
53
380
|
return;
|
|
54
381
|
}
|
|
55
382
|
options.onClick(event);
|
|
56
|
-
if (isToggle(p)) options.
|
|
383
|
+
if (isToggle(p)) options.onChange(!p.selected);
|
|
57
384
|
}
|
|
58
385
|
};
|
|
59
386
|
}
|
|
@@ -81,23 +408,50 @@ const iconButtonDefaults = {
|
|
|
81
408
|
softDisabled: false,
|
|
82
409
|
type: "button"
|
|
83
410
|
};
|
|
411
|
+
/**
|
|
412
|
+
* How the adapter renders the icon button's parts. The icon holds the selected icon when
|
|
413
|
+
* `showsSelectedIcon()`, otherwise the default one.
|
|
414
|
+
*/
|
|
415
|
+
const iconButtonSkeleton = {
|
|
416
|
+
part: "root",
|
|
417
|
+
tag: "button",
|
|
418
|
+
children: [{
|
|
419
|
+
part: "touch",
|
|
420
|
+
tag: "span"
|
|
421
|
+
}, {
|
|
422
|
+
part: "icon",
|
|
423
|
+
tag: "span",
|
|
424
|
+
content: true
|
|
425
|
+
}]
|
|
426
|
+
};
|
|
84
427
|
function createIconButton(options) {
|
|
85
428
|
const button = createButton({
|
|
86
|
-
...options,
|
|
87
429
|
props: () => ({
|
|
88
430
|
...options.props(),
|
|
89
431
|
variant: options.props().variant
|
|
90
|
-
})
|
|
432
|
+
}),
|
|
433
|
+
onClick: options.onClick,
|
|
434
|
+
onChange: options.onChange
|
|
91
435
|
});
|
|
92
436
|
return {
|
|
93
|
-
|
|
437
|
+
parts: {
|
|
438
|
+
root() {
|
|
439
|
+
const p = options.props();
|
|
440
|
+
const root = button.parts.root();
|
|
441
|
+
return {
|
|
442
|
+
...root,
|
|
443
|
+
class: `${root.class} rio-icon-button`,
|
|
444
|
+
"aria-label": p.label,
|
|
445
|
+
"data-width": p.width ?? iconButtonDefaults.width,
|
|
446
|
+
"data-variant": p.variant ?? iconButtonDefaults.variant
|
|
447
|
+
};
|
|
448
|
+
},
|
|
449
|
+
touch: button.parts.touch,
|
|
450
|
+
icon: button.parts.icon
|
|
451
|
+
},
|
|
452
|
+
showsSelectedIcon() {
|
|
94
453
|
const p = options.props();
|
|
95
|
-
return
|
|
96
|
-
...button.rootAttrs(),
|
|
97
|
-
"aria-label": p.label,
|
|
98
|
-
"data-width": p.width ?? iconButtonDefaults.width,
|
|
99
|
-
"data-variant": p.variant ?? iconButtonDefaults.variant
|
|
100
|
-
};
|
|
454
|
+
return !!(p.toggle && p.selected && options.content?.().selectedIcon);
|
|
101
455
|
},
|
|
102
456
|
handleClick: button.handleClick
|
|
103
457
|
};
|
|
@@ -219,7 +573,7 @@ const PADDING = 10;
|
|
|
219
573
|
const SOFT_EDGE_RATIO = .35;
|
|
220
574
|
const SOFT_EDGE_MINIMUM = 75;
|
|
221
575
|
const isDisabled = (el) => el.matches(":disabled, [aria-disabled=\"true\"]");
|
|
222
|
-
const reducedMotion
|
|
576
|
+
const reducedMotion = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
223
577
|
const setPressed = (els, pressed) => {
|
|
224
578
|
for (const el of els) if (pressed) el.dataset.pressed = "";
|
|
225
579
|
else delete el.dataset.pressed;
|
|
@@ -264,7 +618,7 @@ function attachRipple(element, options = {}) {
|
|
|
264
618
|
animation?.cancel();
|
|
265
619
|
animation = surface.animate([frame(from, radius.from), frame(to, radius.to)], {
|
|
266
620
|
pseudoElement: "::after",
|
|
267
|
-
duration: reducedMotion
|
|
621
|
+
duration: reducedMotion() ? 0 : GROW_MS,
|
|
268
622
|
easing: GROW_EASING,
|
|
269
623
|
fill: "forwards"
|
|
270
624
|
});
|
|
@@ -337,138 +691,13 @@ function attachRipple(element, options = {}) {
|
|
|
337
691
|
element.removeEventListener("click", onClick);
|
|
338
692
|
} };
|
|
339
693
|
}
|
|
340
|
-
// src/primitives/spring/spring.ts
|
|
341
|
-
/**
|
|
342
|
-
* Where a spring is `t` seconds after it starts at `from` with `velocity`, heading to `to`.
|
|
343
|
-
*/
|
|
344
|
-
function solveSpring({ dampingRatio: zeta, stiffness }, from, to, velocity, t) {
|
|
345
|
-
const w0 = Math.sqrt(stiffness);
|
|
346
|
-
const x0 = from - to;
|
|
347
|
-
const v0 = velocity;
|
|
348
|
-
if (Math.abs(zeta - 1) < 1e-6) {
|
|
349
|
-
const b = v0 + w0 * x0;
|
|
350
|
-
const decay = Math.exp(-w0 * t);
|
|
351
|
-
return {
|
|
352
|
-
value: to + (x0 + b * t) * decay,
|
|
353
|
-
velocity: (b - w0 * (x0 + b * t)) * decay
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
if (zeta < 1) {
|
|
357
|
-
const a = zeta * w0;
|
|
358
|
-
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
359
|
-
const b = (v0 + a * x0) / wd;
|
|
360
|
-
const decay = Math.exp(-a * t);
|
|
361
|
-
const cos = Math.cos(wd * t);
|
|
362
|
-
const sin = Math.sin(wd * t);
|
|
363
|
-
return {
|
|
364
|
-
value: to + decay * (x0 * cos + b * sin),
|
|
365
|
-
velocity: decay * ((wd * b - a * x0) * cos - (a * b + wd * x0) * sin)
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
const root = w0 * Math.sqrt(zeta * zeta - 1);
|
|
369
|
-
const r1 = -zeta * w0 + root;
|
|
370
|
-
const r2 = -zeta * w0 - root;
|
|
371
|
-
const c2 = (v0 - r1 * x0) / (r2 - r1);
|
|
372
|
-
const c1 = x0 - c2;
|
|
373
|
-
const e1 = Math.exp(r1 * t);
|
|
374
|
-
const e2 = Math.exp(r2 * t);
|
|
375
|
-
return {
|
|
376
|
-
value: to + c1 * e1 + c2 * e2,
|
|
377
|
-
velocity: r1 * c1 * e1 + r2 * c2 * e2
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
const now = () => performance.now() / 1e3;
|
|
381
|
-
/** A spring that animates a single number with `requestAnimationFrame`. */
|
|
382
|
-
function createSpring(options) {
|
|
383
|
-
const precision = options.precision ?? .01;
|
|
384
|
-
let from = options.value;
|
|
385
|
-
let target = options.value;
|
|
386
|
-
let startVelocity = 0;
|
|
387
|
-
let params;
|
|
388
|
-
let startedAt = 0;
|
|
389
|
-
let frame;
|
|
390
|
-
const state = () => params && frame !== void 0 ? solveSpring(params, from, target, startVelocity, now() - startedAt) : {
|
|
391
|
-
value: target,
|
|
392
|
-
velocity: 0
|
|
393
|
-
};
|
|
394
|
-
const settle = () => {
|
|
395
|
-
if (frame !== void 0) cancelAnimationFrame(frame);
|
|
396
|
-
frame = void 0;
|
|
397
|
-
from = target;
|
|
398
|
-
startVelocity = 0;
|
|
399
|
-
options.onUpdate(target);
|
|
400
|
-
options.onRest?.();
|
|
401
|
-
};
|
|
402
|
-
const tick = () => {
|
|
403
|
-
const { value, velocity } = state();
|
|
404
|
-
if (Math.abs(value - target) < precision && Math.abs(velocity) < precision * 10) {
|
|
405
|
-
settle();
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
options.onUpdate(value);
|
|
409
|
-
frame = requestAnimationFrame(tick);
|
|
410
|
-
};
|
|
411
|
-
return {
|
|
412
|
-
get value() {
|
|
413
|
-
return state().value;
|
|
414
|
-
},
|
|
415
|
-
get velocity() {
|
|
416
|
-
return state().velocity;
|
|
417
|
-
},
|
|
418
|
-
get target() {
|
|
419
|
-
return target;
|
|
420
|
-
},
|
|
421
|
-
get moving() {
|
|
422
|
-
return frame !== void 0;
|
|
423
|
-
},
|
|
424
|
-
to(next, nextParams, velocity) {
|
|
425
|
-
const current = state();
|
|
426
|
-
from = current.value;
|
|
427
|
-
startVelocity = velocity ?? current.velocity;
|
|
428
|
-
target = next;
|
|
429
|
-
params = nextParams;
|
|
430
|
-
startedAt = now();
|
|
431
|
-
if (frame === void 0) frame = requestAnimationFrame(tick);
|
|
432
|
-
},
|
|
433
|
-
jump(value) {
|
|
434
|
-
target = value;
|
|
435
|
-
settle();
|
|
436
|
-
},
|
|
437
|
-
stop() {
|
|
438
|
-
const { value } = state();
|
|
439
|
-
if (frame !== void 0) cancelAnimationFrame(frame);
|
|
440
|
-
frame = void 0;
|
|
441
|
-
from = target = value;
|
|
442
|
-
startVelocity = 0;
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Reads a motion token's spring from CSS variables on an element, for example
|
|
448
|
-
* `--rio-motion-fast-spatial-damping` and `-stiffness`. Reading them from the element, rather than
|
|
449
|
-
* from the tokens package, lets a subtree that swaps motion schemes change its springs too.
|
|
450
|
-
* Returns `undefined` when the theme doesn't define them.
|
|
451
|
-
*/
|
|
452
|
-
function readSpringParams(element, token) {
|
|
453
|
-
const style = getComputedStyle(element);
|
|
454
|
-
const dampingRatio = parseFloat(style.getPropertyValue(`--rio-motion-${token}-damping`));
|
|
455
|
-
const stiffness = parseFloat(style.getPropertyValue(`--rio-motion-${token}-stiffness`));
|
|
456
|
-
return Number.isFinite(dampingRatio) && Number.isFinite(stiffness) ? {
|
|
457
|
-
dampingRatio,
|
|
458
|
-
stiffness
|
|
459
|
-
} : void 0;
|
|
460
|
-
}
|
|
461
694
|
// src/primitives/spring-property/spring-property.ts
|
|
462
|
-
/** The
|
|
463
|
-
const
|
|
464
|
-
"
|
|
465
|
-
"
|
|
466
|
-
"
|
|
467
|
-
"data-shape",
|
|
468
|
-
"data-size",
|
|
469
|
-
"data-icon"
|
|
695
|
+
/** The transition events that report a change of the target. */
|
|
696
|
+
const EVENTS = [
|
|
697
|
+
"transitionrun",
|
|
698
|
+
"transitionend",
|
|
699
|
+
"transitioncancel"
|
|
470
700
|
];
|
|
471
|
-
const reducedMotion = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
472
701
|
function attachSpringProperty(element, options) {
|
|
473
702
|
const token = options.token ?? "fast-spatial";
|
|
474
703
|
const read = () => parseFloat(getComputedStyle(element).getPropertyValue(options.target));
|
|
@@ -485,16 +714,20 @@ function attachSpringProperty(element, options) {
|
|
|
485
714
|
const target = read();
|
|
486
715
|
if (!Number.isFinite(target)) return;
|
|
487
716
|
const params = readSpringParams(element, token);
|
|
488
|
-
if (!params ||
|
|
717
|
+
if (!params || prefersReducedMotion()) spring.jump(target);
|
|
489
718
|
else spring.to(target, params, velocity);
|
|
490
719
|
};
|
|
491
|
-
const
|
|
720
|
+
const onTransition = (event) => {
|
|
721
|
+
if (event.target !== element || event.propertyName !== options.target) return;
|
|
492
722
|
if (!held && read() !== spring.target) go();
|
|
723
|
+
};
|
|
724
|
+
for (const type of EVENTS) element.addEventListener(type, onTransition);
|
|
725
|
+
const resync = new ResizeObserver(() => {
|
|
726
|
+
if (held || spring.moving) return;
|
|
727
|
+
const now = read();
|
|
728
|
+
if (Number.isFinite(now) && now !== spring.target) spring.jump(now);
|
|
493
729
|
});
|
|
494
|
-
|
|
495
|
-
attributes: true,
|
|
496
|
-
attributeFilter: options.watch ?? STATE_ATTRIBUTES
|
|
497
|
-
});
|
|
730
|
+
resync.observe(element);
|
|
498
731
|
return {
|
|
499
732
|
get value() {
|
|
500
733
|
return spring.value;
|
|
@@ -508,25 +741,182 @@ function attachSpringProperty(element, options) {
|
|
|
508
741
|
go(velocity);
|
|
509
742
|
},
|
|
510
743
|
detach() {
|
|
511
|
-
|
|
744
|
+
for (const type of EVENTS) element.removeEventListener(type, onTransition);
|
|
745
|
+
resync.disconnect();
|
|
512
746
|
spring.stop();
|
|
513
747
|
options.apply(void 0);
|
|
514
748
|
}
|
|
515
749
|
};
|
|
516
750
|
}
|
|
517
751
|
// src/primitives/shape-morph/shape-morph.ts
|
|
752
|
+
const CORNERS = {
|
|
753
|
+
start: ["border-start-start-radius", "border-end-start-radius"],
|
|
754
|
+
end: ["border-start-end-radius", "border-end-end-radius"]
|
|
755
|
+
};
|
|
518
756
|
function attachShapeMorph(element, options = {}) {
|
|
519
|
-
const radius = attachSpringProperty(element, {
|
|
520
|
-
...options,
|
|
521
|
-
target: "--rio-shape-morph-target",
|
|
522
|
-
apply: (value) => value === void 0 ? element.style.removeProperty("border-radius") : element.style.setProperty("border-radius", `${value}px`)
|
|
523
|
-
});
|
|
524
757
|
element.dataset.shapeMorph = "";
|
|
758
|
+
const springs = ["start", "end"].map((side) => attachSpringProperty(element, {
|
|
759
|
+
...options,
|
|
760
|
+
target: `--rio-shape-morph-${side}-target`,
|
|
761
|
+
apply: (value) => {
|
|
762
|
+
for (const corner of CORNERS[side]) if (value === void 0) element.style.removeProperty(corner);
|
|
763
|
+
else element.style.setProperty(corner, `${value}px`);
|
|
764
|
+
}
|
|
765
|
+
}));
|
|
525
766
|
return { detach() {
|
|
526
|
-
|
|
767
|
+
for (const spring of springs) spring.detach();
|
|
527
768
|
delete element.dataset.shapeMorph;
|
|
528
769
|
} };
|
|
529
770
|
}
|
|
771
|
+
// src/split-button/menu-icon.ts
|
|
772
|
+
const inlineVar$1 = (el, name, unit) => (value) => value === void 0 ? el.style.removeProperty(name) : el.style.setProperty(name, `${value}${unit}`);
|
|
773
|
+
/** Attach to the trailing button. */
|
|
774
|
+
function attachSplitButtonMenuIcon(trailing) {
|
|
775
|
+
const token = "standard-fast-spatial";
|
|
776
|
+
const turn = attachSpringProperty(trailing, {
|
|
777
|
+
target: "--rio-split-button-menu-turn-target",
|
|
778
|
+
token,
|
|
779
|
+
apply: inlineVar$1(trailing, "--_menu-turn", "deg")
|
|
780
|
+
});
|
|
781
|
+
const shift = attachSpringProperty(trailing, {
|
|
782
|
+
target: "--rio-split-button-menu-shift-target",
|
|
783
|
+
token,
|
|
784
|
+
apply: inlineVar$1(trailing, "--_menu-shift", "px")
|
|
785
|
+
});
|
|
786
|
+
return { detach() {
|
|
787
|
+
turn.detach();
|
|
788
|
+
shift.detach();
|
|
789
|
+
} };
|
|
790
|
+
}
|
|
791
|
+
// src/split-button/split-button.ts
|
|
792
|
+
/** Split button colors: the button's, without text. */
|
|
793
|
+
const SPLIT_BUTTON_VARIANTS = [
|
|
794
|
+
"filled",
|
|
795
|
+
"tonal",
|
|
796
|
+
"outlined",
|
|
797
|
+
"elevated"
|
|
798
|
+
];
|
|
799
|
+
const splitButtonDefaults = {
|
|
800
|
+
variant: "filled",
|
|
801
|
+
size: "small",
|
|
802
|
+
open: false,
|
|
803
|
+
popup: "menu",
|
|
804
|
+
disabled: false,
|
|
805
|
+
softDisabled: false,
|
|
806
|
+
type: "button"
|
|
807
|
+
};
|
|
808
|
+
/**
|
|
809
|
+
* How the adapter renders the split button's parts. The icon and the label hold the leading
|
|
810
|
+
* button's content; the menu icon holds the expand icon the app provides.
|
|
811
|
+
*/
|
|
812
|
+
const splitButtonSkeleton = {
|
|
813
|
+
part: "root",
|
|
814
|
+
tag: "div",
|
|
815
|
+
children: [{
|
|
816
|
+
part: "leading",
|
|
817
|
+
tag: "button",
|
|
818
|
+
children: [
|
|
819
|
+
{
|
|
820
|
+
part: "touch",
|
|
821
|
+
tag: "span"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
part: "icon",
|
|
825
|
+
tag: "span",
|
|
826
|
+
when: "showsIcon",
|
|
827
|
+
content: true
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
part: "label",
|
|
831
|
+
tag: "span",
|
|
832
|
+
when: "showsLabel",
|
|
833
|
+
content: true
|
|
834
|
+
}
|
|
835
|
+
]
|
|
836
|
+
}, {
|
|
837
|
+
part: "trailing",
|
|
838
|
+
tag: "button",
|
|
839
|
+
children: [{
|
|
840
|
+
part: "touch",
|
|
841
|
+
tag: "span"
|
|
842
|
+
}, {
|
|
843
|
+
part: "menuIcon",
|
|
844
|
+
tag: "span",
|
|
845
|
+
content: true
|
|
846
|
+
}]
|
|
847
|
+
}]
|
|
848
|
+
};
|
|
849
|
+
function createSplitButton(options) {
|
|
850
|
+
const props = () => ({
|
|
851
|
+
...splitButtonDefaults,
|
|
852
|
+
...omitUndefined(options.props())
|
|
853
|
+
});
|
|
854
|
+
const content = () => options.content?.() ?? {
|
|
855
|
+
icon: false,
|
|
856
|
+
label: true
|
|
857
|
+
};
|
|
858
|
+
const shared = () => {
|
|
859
|
+
const p = props();
|
|
860
|
+
return {
|
|
861
|
+
variant: p.variant,
|
|
862
|
+
size: p.size,
|
|
863
|
+
disabled: p.disabled,
|
|
864
|
+
softDisabled: p.softDisabled
|
|
865
|
+
};
|
|
866
|
+
};
|
|
867
|
+
const leading = createButton({
|
|
868
|
+
props: () => ({
|
|
869
|
+
...shared(),
|
|
870
|
+
type: props().type
|
|
871
|
+
}),
|
|
872
|
+
onClick: options.onClick,
|
|
873
|
+
onChange: () => {}
|
|
874
|
+
});
|
|
875
|
+
const trailing = createButton({
|
|
876
|
+
props: () => ({
|
|
877
|
+
...shared(),
|
|
878
|
+
type: "button"
|
|
879
|
+
}),
|
|
880
|
+
onClick: () => options.onChange(!props().open),
|
|
881
|
+
onChange: () => {}
|
|
882
|
+
});
|
|
883
|
+
return {
|
|
884
|
+
parts: {
|
|
885
|
+
root: () => ({ class: "rio-split-button" }),
|
|
886
|
+
leading() {
|
|
887
|
+
const root = leading.parts.root();
|
|
888
|
+
return {
|
|
889
|
+
...root,
|
|
890
|
+
class: `${root.class} rio-split-button__leading`,
|
|
891
|
+
"aria-label": content().label ? void 0 : props().label
|
|
892
|
+
};
|
|
893
|
+
},
|
|
894
|
+
trailing() {
|
|
895
|
+
const p = props();
|
|
896
|
+
const root = trailing.parts.root();
|
|
897
|
+
return {
|
|
898
|
+
...root,
|
|
899
|
+
class: `${root.class} rio-split-button__trailing`,
|
|
900
|
+
"aria-label": p.menuLabel,
|
|
901
|
+
"aria-haspopup": p.popup,
|
|
902
|
+
"aria-expanded": String(p.open),
|
|
903
|
+
"data-open": p.open ? "" : void 0
|
|
904
|
+
};
|
|
905
|
+
},
|
|
906
|
+
touch: leading.parts.touch,
|
|
907
|
+
icon: leading.parts.icon,
|
|
908
|
+
label: leading.parts.label,
|
|
909
|
+
menuIcon: () => ({
|
|
910
|
+
class: "rio-split-button__menu-icon",
|
|
911
|
+
"aria-hidden": "true"
|
|
912
|
+
})
|
|
913
|
+
},
|
|
914
|
+
showsIcon: () => content().icon,
|
|
915
|
+
showsLabel: () => content().label,
|
|
916
|
+
handleLeadingClick: leading.handleClick,
|
|
917
|
+
handleTrailingClick: trailing.handleClick
|
|
918
|
+
};
|
|
919
|
+
}
|
|
530
920
|
// src/switch/handle.ts
|
|
531
921
|
/** A release faster than this (px/s) picks the side it's heading to, wherever the handle is. */
|
|
532
922
|
const FLING_VELOCITY = 300;
|
|
@@ -559,7 +949,7 @@ function attachSwitchHandle(root, options) {
|
|
|
559
949
|
onEnd({ velocityX }) {
|
|
560
950
|
const velocity = direction * velocityX;
|
|
561
951
|
const next = Math.abs(velocity) > FLING_VELOCITY ? velocity > 0 : x.value > (off + on) / 2;
|
|
562
|
-
if (next !== options.selected()) options.
|
|
952
|
+
if (next !== options.selected()) options.onChange(next);
|
|
563
953
|
x.release(velocity);
|
|
564
954
|
},
|
|
565
955
|
onCancel() {
|
|
@@ -578,28 +968,80 @@ const switchDefaults = {
|
|
|
578
968
|
disabled: false,
|
|
579
969
|
softDisabled: false
|
|
580
970
|
};
|
|
971
|
+
/**
|
|
972
|
+
* How the adapter renders the switch's parts. The state part is the circle where the state layer
|
|
973
|
+
* and the ripple draw. The icon holds the selected icon when `showsSelectedIcon()`, otherwise the
|
|
974
|
+
* unselected one.
|
|
975
|
+
*/
|
|
976
|
+
const switchSkeleton = {
|
|
977
|
+
part: "root",
|
|
978
|
+
tag: "button",
|
|
979
|
+
children: [
|
|
980
|
+
{
|
|
981
|
+
part: "touch",
|
|
982
|
+
tag: "span"
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
part: "state",
|
|
986
|
+
tag: "span"
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
part: "handle",
|
|
990
|
+
tag: "span",
|
|
991
|
+
children: [{
|
|
992
|
+
part: "icon",
|
|
993
|
+
tag: "span",
|
|
994
|
+
when: "showsIcon",
|
|
995
|
+
content: true
|
|
996
|
+
}]
|
|
997
|
+
}
|
|
998
|
+
]
|
|
999
|
+
};
|
|
581
1000
|
function createSwitch(options) {
|
|
1001
|
+
const showsIcon = () => {
|
|
1002
|
+
const content = options.content?.();
|
|
1003
|
+
return !!(options.props().selected ? content?.selectedIcon : content?.unselectedIcon);
|
|
1004
|
+
};
|
|
582
1005
|
return {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
1006
|
+
parts: {
|
|
1007
|
+
root() {
|
|
1008
|
+
const p = options.props();
|
|
1009
|
+
const selected = String(!!p.selected);
|
|
1010
|
+
return {
|
|
1011
|
+
class: "rio-switch rio-focus-ring",
|
|
1012
|
+
type: "button",
|
|
1013
|
+
role: "switch",
|
|
1014
|
+
"aria-checked": selected,
|
|
1015
|
+
disabled: p.disabled ? "" : void 0,
|
|
1016
|
+
"aria-disabled": p.softDisabled && !p.disabled ? "true" : void 0,
|
|
1017
|
+
"data-selected": selected,
|
|
1018
|
+
"data-icon": showsIcon() ? "" : void 0
|
|
1019
|
+
};
|
|
1020
|
+
},
|
|
1021
|
+
touch: () => ({
|
|
1022
|
+
class: "rio-switch__touch",
|
|
1023
|
+
"aria-hidden": "true"
|
|
1024
|
+
}),
|
|
1025
|
+
state: () => ({
|
|
1026
|
+
class: "rio-switch__state",
|
|
1027
|
+
"aria-hidden": "true"
|
|
1028
|
+
}),
|
|
1029
|
+
handle: () => ({
|
|
1030
|
+
class: "rio-switch__handle",
|
|
1031
|
+
"aria-hidden": "true"
|
|
1032
|
+
}),
|
|
1033
|
+
icon: () => ({ class: "rio-switch__icon" })
|
|
594
1034
|
},
|
|
1035
|
+
showsIcon,
|
|
1036
|
+
showsSelectedIcon: () => !!(options.props().selected && options.content?.().selectedIcon),
|
|
595
1037
|
handleClick(event) {
|
|
596
1038
|
const p = options.props();
|
|
597
1039
|
if (p.disabled || p.softDisabled) {
|
|
598
1040
|
event.preventDefault();
|
|
599
1041
|
return;
|
|
600
1042
|
}
|
|
601
|
-
options.
|
|
1043
|
+
options.onChange(!p.selected);
|
|
602
1044
|
}
|
|
603
1045
|
};
|
|
604
1046
|
}
|
|
605
|
-
export { BUTTON_SIZES, BUTTON_VARIANTS, ICON_BUTTON_VARIANTS, ICON_BUTTON_WIDTHS,
|
|
1047
|
+
export { BUTTON_GROUP_SELECTIONS, BUTTON_GROUP_VARIANTS, BUTTON_SIZES, BUTTON_VARIANTS, ICON_BUTTON_VARIANTS, ICON_BUTTON_WIDTHS, SPLIT_BUTTON_VARIANTS, attachButtonGroup, attachDrag, attachRipple, attachShapeMorph, attachSplitButtonMenuIcon, attachSpringProperty, attachSwitchHandle, buttonDefaults, buttonGroupDefaults, buttonGroupSkeleton, buttonSkeleton, createButton, createButtonGroup, createIconButton, createSplitButton, createSpring, createSwitch, iconButtonDefaults, iconButtonSkeleton, prefersReducedMotion, readSpringParams, solveSpring, splitButtonDefaults, splitButtonSkeleton, switchDefaults, switchSkeleton };
|